> ## Documentation Index
> Fetch the complete documentation index at: https://doc.haipay.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription

> Understand HaiPay subscription integration modes, business requirements, launch rules, and core API entry points before you start building recurring card payments.

HaiPay subscription payments are designed for memberships, recurring services, and auto-renew billing. This page now acts as a cleaner landing page: it helps you choose an integration mode, understand launch constraints, and jump into the right API Reference pages instead of scanning a long wall of duplicated parameter tables.

<Columns cols={3}>
  <Card title="Quick Start" icon="rocket" href="#quick-start">
    Review the integration path, required product capabilities, and reading order.
  </Card>

  <Card title="Integration Modes" icon="layer-group" href="#integration-modes">
    Compare Apple Pay / Google Pay, card component, and combined mode.
  </Card>

  <Card title="Core APIs" icon="list-check" href="#core-apis">
    Jump directly to create, query, cancel, and refund endpoints.
  </Card>
</Columns>

<Warning title="Required product and service capabilities before launch">
  * Provide a clear subscription entry, billing-cycle explanation, and cancellation entry.
  * Notify users outside the platform after subscription activation and before or after each recurring charge.
  * Show a subscription service agreement and obtain explicit user consent before starting recurring billing.
  * Keep customer support available, at minimum during the operating hours of your main markets.
</Warning>

<Tip title="Credit card disputes and fee reminder">
  * Consumers may dispute card charges within 180 days after the transaction date.
  * Each dispute incurs a 20 USD dispute handling fee.
  * If you choose to defend the dispute, an additional 20 USD rebuttal fee applies.
  * If the rebuttal succeeds, HaiPay refunds the rebuttal fee; the original dispute fee is not refunded by default.
</Tip>

<Danger title="PCI DSS compliance requirement">
  PCI DSS strictly prohibits embedding card input pages inside `WebView` or `iframe`. Always collect card details and complete authorization in a trusted browser environment to avoid compliance and data-security issues.
</Danger>

<a id="quick-start" />

## Quick Start

<Steps>
  <Step title="Confirm the business model and prerequisites">
    Start with the [API Description Guide](/docs/en/guide/api_description_guide) and the [Subscription Process](/docs/en/api/version2/subscription-process) to confirm that your scenario is recurring billing rather than a one-time payment flow.
  </Step>

  <Step title="Choose the right integration mode">
    Subscription payments offer two integration modes: **PayUrl Redirect** (server obtains an authorization URL and redirects the user to HaiPay's hosted payment page) and **JS CDN Embed** (uses a client token to embed payment components directly in your own page). Choose based on your checkout design and frontend capabilities.
  </Step>

  <Step title="Connect APIs, callbacks, and cancellation">
    After creating a subscription, you still need subscription query, recurring callback handling, and a user-facing cancellation flow. The detailed fields and debug entry points are linked in the API cards below.
  </Step>
</Steps>

<a id="integration-modes" />

## Integration Modes

Subscription payments offer two integration modes. The key difference is who hosts the payment page:

| Comparison         | PayUrl Redirect                                  | JS Embed                                              |
| :----------------- | :----------------------------------------------- | :---------------------------------------------------- |
| Payment page       | Redirects to HaiPay's hosted payment page        | Embeds payment components in your own page            |
| Frontend effort    | Almost zero, just handle the redirect            | Requires importing JS scripts and listening to events |
| UI control         | Low, page style controlled by HaiPay             | High, customizable theme, layout, and interactions    |
| Key response field | Uses `payUrl` from the response                  | Uses `clientToken` from the response                  |
| Best for           | Quick integration, no custom payment page needed | Custom checkout, seamless in-page payment experience  |

<Tabs sync={false}>
  <Tab title="PayUrl Redirect" icon="arrow-up-right-from-square">
    The simplest integration path. After your server calls the subscription create API, use the returned `payUrl` to redirect the user to HaiPay's hosted payment page where they complete authorization and the first payment.

    **Integration Flow**

    <Steps>
      <Step title="Server calls the subscription create API">
        Your server sends a [Subscription Create](/docs/en/V20260701/api-reference/subscription/api/subscriptionApply) request to HaiPay with the required parameters such as amount, cycle, and callback URLs.
      </Step>

      <Step title="Get payUrl and redirect">
        The API response includes a `payUrl` field. Redirect the user's browser to that URL.
      </Step>

      <Step title="User completes payment">
        The user fills in card details and authorizes the subscription on HaiPay's hosted page. After payment, HaiPay redirects the user back to your `callBackUrl` and sends an asynchronous notification to your `notifyUrl`.
      </Step>
    </Steps>

    **Example code (frontend redirect after server obtains payUrl)**

    ```javascript theme={null}
    // Assuming your server has called the subscription create API
    // and returned the payUrl to the frontend
    const payUrl = response.data.payUrl;

    // Redirect in the current window
    window.location.href = payUrl;

    // Or open in a new window
    // window.open(payUrl, '_blank');
    ```

    **When to choose it**

    * You want the fastest possible subscription integration with minimal frontend changes.
    * You have no specific customization requirements for the payment page UI.
    * For mobile H5 scenarios, the redirect experience is usually sufficient.
  </Tab>

  <Tab title="JS Embed" icon="code">
    Embed payment components directly in your own page using HaiPay JS scripts. After your server calls the subscription create API, use the returned `clientToken` to initialize the frontend component. Users complete payment without leaving your page.

    The JS offers the following component types. Choose based on your checkout design:

    <Tabs sync={false}>
      <Tab title="Custom Combined Mode" icon="table-cells-large">
        Similar to Combined Mode, but additionally supports switching between one-time payment and subscription payment via the `mode` parameter. Ideal for merchants who need to manage multiple payment scenarios with one integration.

        **Script**

        `https://cashier.haipay.top/js/composite-component_1.0.0.min.js`

        **Parameters**

        | Parameter             | Required | Description                                                                 |
        | :-------------------- | :------- | :-------------------------------------------------------------------------- |
        | `sandbox`             | Yes      | `true` for test, `false` for production                                     |
        | `clientToken`         | Yes      | Client token returned by the subscription create API                        |
        | `mode`                | Yes      | Payment scenario: `payment` (one-time) or `subscription` (recurring)        |
        | `showPayButton`       | No       | Whether to display the credit card submit button. Default: `false` (hidden) |
        | `themeMode`           | No       | Theme mode, `light` or `dark`, default `light`                              |
        | `paymentMethodConfig` | No       | Payment-method list, default `["GOOGLE_PAY","APPLE_PAY","CREDIT_CARD"]`     |
        | `applePayButtonType`  | No       | Apple Pay button text, e.g., `buy`, `subscribe`                             |
        | `googlePayButtonType` | No       | Google Pay button text, e.g., `buy`, `subscribe`                            |
        | `cardPosition`        | No       | Credit card placement position, default `bottom`, options: `bottom`, `top`  |

        **Events**

        * `ready` — Component rendered, ready for interaction
        * `error` — Initialization failed or payment error occurred
        * `confirm` — Form submission result (success/failure)
        * `paymentStatus` — Payment result notification, failure will return error message (if not listened, the component will handle result display automatically)
        * `paymentMethodType` — Currently selected payment method

        **When to choose it**

        * When you need to support both one-time payment and subscription payment in the same codebase.
        * If you only enable certain payment methods, just pass the required combination.

        ```javascript theme={null}
        <script src="https://cashier.haipay.top/js/composite-component_1.0.0.min.js"></script>

        <div>
          <div id="paymentId"></div>
        </div>

        const elements = haiPay.create({
          mode: 'payment',
          sandbox: true,
          clientToken: 'your-client-token',
          themeMode: 'light',
          paymentMethodConfig: ['GOOGLE_PAY', 'APPLE_PAY', 'CREDIT_CARD']
        });

        elements.mount('#paymentId');

        elements.on('ready', (event) => {
          console.log('rendered successfully:', event);
        });

        elements.on('error', (event) => {
          console.error('Payment error:', event);
        });

        elements.on('paymentMethodType', (event) => {
          console.log('payment method type', event);
        });

        elements.submitCardPayment();

        elements.on('confirm', (event) => {
          console.log('confirm:', event);
        });

        elements.on('paymentStatus', (event, message) => {
          console.log('payment status:', event, message);
        });

        ```
      </Tab>

      <Tab title="Apple Pay / Google Pay (Deprecated Soon)" icon="wallet">
        <Warning>This component will be deprecated soon. Please use "Custom Combined Mode" instead.</Warning>

        Best for teams that already support wallet-based checkout and want a fast subscription authorization experience.

        **Recommended script**

        `https://cashier.haipay.top/js/applePayGooglePaySubscribe_1.0.0.min.js`

        **Key parameters**

        | Parameter      | Required | Description                              |
        | :------------- | :------- | :--------------------------------------- |
        | `sandbox`      | Yes      | `true` for test, `false` for production  |
        | `clientToken`  | Yes      | Client token                             |
        | `buttonType`   | No       | Button text such as `buy` or `subscribe` |
        | `buttonTheme`  | No       | Button theme such as `black` or `white`  |
        | `buttonHeight` | No       | Button height, default `50`              |

        **Typical events**

        * `ready`: button rendered
        * `error`: initialization or payment error
        * `paymentStatus`: payment result callback (If you do not listen to this event, the component will process the payment result by default)
        * `paymentMethodType` — Currently selected payment method

        ```javascript theme={null}
        <script src="https://cashier.haipay.top/js/applePayGooglePaySubscribe_1.0.0.min.js"></script>

        <div>
          <div id="applePayGooglePayId"></div>
        </div>

        const elements = applePayGooglePaySubscribe.create('applePayGooglePaySubscribe', {
          sandbox: true,
          clientToken: 'your-client-token',
          buttonType: 'subscribe',
          buttonTheme: 'black',
          buttonHeight: 50
        });

        elements.mount('#applePayGooglePayId');

        elements.on('ready', (data) => {
          console.log('Button rendered successfully:', data);
        });

        elements.on('error', (data) => {
          console.error('Payment error:', data);
        });

        elements.on('paymentMethodType', (event) => {
          console.log('payment method type', event);
        });

        elements.on('paymentStatus', (data) => {
          console.log('payment status:', data);
        });
        ```
      </Tab>

      <Tab title="Credit Card (Deprecated Soon)" icon="credit-card">
        <Warning>This component will be deprecated soon. Please use "Custom Combined Mode" instead.</Warning>

        Best when you want to render the subscription card form directly and control the submit action yourself.

        **Recommended script**

        `https://cashier.haipay.top/js/creditCardSubscription_1.0.0.min.js`

        **Key parameters**

        | Parameter     | Required | Description                             |
        | :------------ | :------- | :-------------------------------------- |
        | `sandbox`     | Yes      | `true` for test, `false` for production |
        | `clientToken` | Yes      | Client token                            |
        | `cardTheme`   | No       | Form theme, default `white`             |

        **Typical events**

        * `ready`: card component rendered
        * `confirm`: form submission result
        * `paymentStatus`: payment result callback (If you do not listen to this event, the component will process the payment result by default)

        ```javascript theme={null}
        <script src="https://cashier.haipay.top/js/creditCardSubscription_1.0.0.min.js"></script>

        <div>
          <div id="creditCardId"></div>
        </div>

        const elements = creditCardSubscription.create('card', {
          sandbox: true,
          clientToken: 'your-client-token',
          cardTheme: 'white'
        });

        elements.mount('#creditCardId');

        elements.on('ready', (data) => {
          console.log('Card rendered successfully:', data);
        });

        elements.on('error', (data) => {
          console.error('Payment error:', data);
        });

        elements.submit();

        elements.on('confirm', (data) => {
          console.log('confirm:', data);
        });

        elements.on('paymentStatus', (data) => {
          console.log('payment status:', data);
        });
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

<a id="core-apis" />

## Core APIs

For full request fields, response schemas, and live debugging, go directly to the API Reference pages:

<Columns cols={2}>
  <Card title="Subscription Create" icon="circle-plus" href="/docs/en/V20260701/api-reference/subscription/api/subscriptionApply">
    Create a subscription order, launch user authorization, and complete the first subscription payment.
  </Card>

  <Card title="Subscription Query" icon="magnifying-glass" href="/docs/en/api-reference/subscription/api/subscriptionQuery">
    Check subscription status, platform subscription number, and deduction records.
  </Card>

  <Card title="Subscription Cancel" icon="ban" href="/docs/en/api-reference/subscription/api/subscriptionCancel">
    Cancel an active subscription and stop future recurring charges.
  </Card>
</Columns>

## Asynchronous Notifications

When the subscription status changes or a recurring deduction completes, HaiPay pushes an asynchronous notification to the `notifyUrl` provided in the subscription create request.

<Tip title="Receiving Recommendations">
  To ensure extensibility and compatibility, follow these callback parameter reception guidelines:

  1. **Do not** declare a specific POJO object to receive callback parameters.
  2. **Must** use a generic data structure (like JSONObject, Map, etc.) to parse parameters.
  3. This design ensures that if callback fields are extended later, it will not affect your signature verification logic.
</Tip>

After receiving a notification, return **SUCCESS** (uppercase) in the response body.

<Tabs sync={false}>
  <Tab title="Subscription Status Notification" icon="bell">
    Triggered when the subscription status changes (e.g., subscription activated, canceled, failed, etc.).

    | Parameter                | Type    | Description                                                                       |
    | :----------------------- | :------ | :-------------------------------------------------------------------------------- |
    | `type`                   | String  | Notification type, fixed as `SUBSCRIPTION`                                        |
    | `appId`                  | Long    | Business ID                                                                       |
    | `currency`               | String  | Currency                                                                          |
    | `subscriptionOrderId`    | String  | Merchant subscription order ID                                                    |
    | `subscriptionNo`         | String  | Platform subscription number                                                      |
    | `amount`                 | String  | Subscription amount                                                               |
    | `status`                 | Integer | Subscription status (1: Pending, 2: Active, 3: Failed, 4: Canceled, 5: Completed) |
    | `recurringInterval`      | String  | Recurring cycle type (D/W/M/Y)                                                    |
    | `recurringIntervalCount` | Integer | Recurring cycle interval                                                          |
    | `subject`                | String  | Subscription title                                                                |
    | `sign`                   | String  | Signature (verify using the business secret key corresponding to the currency)    |
  </Tab>

  <Tab title="Deduction Notification" icon="money-bill-wave">
    Triggered when each recurring deduction succeeds or fails.

    | Parameter             | Type    | Description                                                                    |
    | :-------------------- | :------ | :----------------------------------------------------------------------------- |
    | `type`                | String  | Notification type, fixed as `SUBSCRIPTIONS_DEDUCT`                             |
    | `appId`               | Long    | Business ID                                                                    |
    | `currency`            | String  | Currency                                                                       |
    | `subscriptionOrderId` | String  | Merchant subscription order ID                                                 |
    | `subscriptionNo`      | String  | Platform subscription number                                                   |
    | `deductNo`            | String  | Deduction number                                                               |
    | `orderNo`             | String  | Platform order number                                                          |
    | `amount`              | String  | Deduction amount for this cycle                                                |
    | `status`              | Integer | Deduction status (2: Success, 3: Failed)                                       |
    | `startTime`           | String  | Deduction cycle start time, format: yyyy-MM-dd HH:mm:ss                        |
    | `endTime`             | String  | Deduction cycle end time, format: yyyy-MM-dd HH:mm:ss                          |
    | `sign`                | String  | Signature (verify using the business secret key corresponding to the currency) |
  </Tab>
</Tabs>

## Key Business Rules

| Item                      | Details                                                                                                                                                                                                                                                                                           |
| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Amount limit              | Subscription amount range is `0.99 - 1000 USD`                                                                                                                                                                                                                                                    |
| Max duration              | Total subscription duration cannot exceed 3 years regardless of cycle unit                                                                                                                                                                                                                        |
| Cycle unit                | Apple Pay does not support `W`; use `D` + `7` for weekly billing                                                                                                                                                                                                                                  |
| Payment type              | Subscription flows use `SUBSCRIPTION`                                                                                                                                                                                                                                                             |
| User notifications        | Notify users after activation and before or after recurring deductions                                                                                                                                                                                                                            |
| Cancellation              | Production environments must provide a clear and accessible cancellation entry                                                                                                                                                                                                                    |
| Payment method parameters | In the subscription create API, `inBankCode` and `paymentMethods` are mutually exclusive: use `inBankCode` for a single payment method code, or `paymentMethods` for a comma-separated list of payment methods. `paymentMethods` currently only supports the front-end component combination mode |

## Test Card Numbers

<Warning>For use in test environment only</Warning>

**Simulate Successful Payment**

Use the following test card number. Enter any 3-digit CVC and any future expiration date:

* `4242424242424242`

**Simulate Payment Failure**

Use the following test card number with invalid data:

* Card Number: `4000000000009995`
* Invalid Month: `13`
* Invalid CVV: `99`

## Common Questions

<AccordionGroup>
  <Accordion title="When should I use this page versus the API Reference?" defaultOpen>
    Use this page to understand the product model, select an integration mode, and review the launch rules. When you are ready to build or debug, move to the [Subscription API Reference](/docs/en/V20260701/api-reference/subscription/api/subscriptionApply) for full field definitions, example payloads, and testing tools.
  </Accordion>

  <Accordion title="Why are cancellation and off-platform notifications mandatory for subscriptions?">
    Subscription payments create a higher dispute risk than one-time payments. Clear authorization, notification, and cancellation controls reduce chargebacks, complaints, and risk-review issues, and they are usually required for a stable production rollout.
  </Accordion>

  <Accordion title="How should I interpret Unsupported region: CN in production?">
    It usually means the current production configuration does not support that region, or the payment-method setup does not match the target market. Confirm your merchant configuration, target market, and payment-method mix first, then contact HaiPay support if needed.
  </Accordion>

  <Accordion title="How should I choose between PayUrl Redirect and JS Embed?">
    If you want the fastest launch with minimal frontend changes, use PayUrl Redirect. If you need a custom checkout UI where users stay on your page, use JS CDN Embed. Both modes use the same subscription create API; the only difference is whether you use the returned `payUrl` or `clientToken`.
  </Accordion>

  <Accordion title="Should I use inBankCode or paymentMethods?">
    These two parameters are mutually exclusive — you cannot pass both, and you must pass at least one. Use `inBankCode` when only a single payment method is needed. Use `paymentMethods` when multiple payment methods are required (e.g., Combined Mode). Note that `paymentMethods` currently only supports the front-end component combination mode; for other modes, continue using `inBankCode`.
  </Accordion>

  <Accordion title="Which mode should I choose for a modern subscription page with the least frontend work?">
    If you go with JS Embed, start with Combined Mode. It gives you a more complete checkout container, lowers the amount of frontend composition work, and is usually easier for users to understand.
  </Accordion>
</AccordionGroup>

## Related Topics

* [Common API](/docs/en/api/version2/CommonApi)
* [HaiPay API Description and Common Rules](/docs/en/guide/api_description_guide)
* [Subscription Payment Flowchart](/docs/en/api/version2/subscription-process)
