Build a subscription integration

Integrate subscription payments with the Onerway Web SDK (SUBSCRIBE), with support for both managed and self-managed renewal models.

Overview

Use the SDK subscription model when you need to complete the initial subscription payment and recurring renewals within your own checkout page. Compared with a standard one-time payment, SDK subscriptions have these key differences:

  • The request must set subProductType to "SUBSCRIBE"
  • You must provide the subscription parameter (a JSON string) to define subscription rules
  • The initial payment may still require a 3DS flow (redirect when status = "R")
  • When card-binding capabilities are enabled, you may receive both BIND_CARD and SALE notifications

Always treat webhooks as the transaction source of truth ( ).

Integration profile

Integration complexity
Integration type
Web SDK (embedded)
Core outputs
contractId / tokenId / subscriptionManageUrl

Best-fit scenarios

  • Activate subscriptions on a merchant-hosted checkout page
  • Handle 3DS, async notifications, and subscription status flow in one design
  • Flexibly choose between managed and self-managed renewals

Key capabilities

  • Enable subscription transactions with subProductType=SUBSCRIBE
  • Support managed (selfExecute=1) and self-managed (selfExecute=2) modes
  • Support trial configuration (trialDays or trialEnd)
  • Distinguish subscription lifecycle events using scenarios
  • Receive subscriptionManageUrl in managed mode

Choose a subscription mode

subscription.selfExecute determines who executes renewals:

  • Renewal executor: Onerway
  • Typical capability boundary: automatic renewals, notification emails, customer management entry (subscriptionManageUrl)
  • Recommended for: teams that want fast launch and lower renewal scheduler development cost

Minimal parameter skeleton (example)

{
  "requestType": "0",
  "merchantCustId": "CUST-10001",
  "selfExecute": "1",
  "mode": "2",
  "frequencyType": "M",
  "frequencyPoint": "1",
  "cycleCount": 12
}
In both modes, you must process the initial transaction result and webhook callbacks correctly. Do not treat SDK synchronous callbacks as the final success signal.

Integration steps

Implement in this order:

  1. Create a SUBSCRIBE transaction on the server
  2. Initialize the SDK on the client (SUBSCRIBE mode)
  3. Handle SDK callbacks and 3DS redirects
  4. Receive and process webhooks idempotently (including scenarios)
  5. Store subscription credentials and drive business state

Create a SUBSCRIBE transaction on the server Server-side

The server request is similar to a standard SDK transaction. Key differences:

  • Set subProductType to "SUBSCRIBE"
  • subscription is required and must be a JSON string
  • Pass merchantCustId explicitly to bind customer and subscription relationship
{
  "merchantNo": "800209",
  "merchantTxnId": "SUB-ORDER-20260313-0001",
  "merchantTxnTime": "2026-03-13 10:00:00",
  "merchantCustId": "CUST-10001",
  "orderAmount": "5.00",
  "orderCurrency": "USD",
  "productType": "CARD",
  "subProductType": "SUBSCRIBE",
  "txnType": "SALE",
  "txnOrderMsg": "{\"appId\":\"YOUR_APP_ID\",\"returnUrl\":\"https://merchant.example.com/return\",\"notifyUrl\":\"https://merchant.example.com/webhook\"}",
  "billingInformation": "{\"email\":\"customer@example.com\",\"country\":\"US\"}",
  "shippingInformation": "{\"email\":\"customer@example.com\",\"country\":\"US\"}",
  "subscription": "{\"requestType\":\"0\",\"merchantCustId\":\"CUST-10001\",\"productName\":\"Pro Plan Monthly\",\"frequencyType\":\"M\",\"frequencyPoint\":\"1\",\"cycleCount\":12,\"selfExecute\":\"1\",\"mode\":\"2\",\"notificationEmail\":\"customer@example.com\"}",
  "sign": "YOUR_SIGNATURE"
}

After order creation succeeds, return these fields to the frontend for SDK initialization:

  • transactionId
  • redirectUrl

Subscription parameter guidance (subscription) Server-side

To avoid confusion, interpret parameters as shared fields + mode-specific fields.

Shared fields (recommended to confirm first in both modes)

Field

requestType

Required

Required

Description

Request type. Initial subscription is typically 0; use other values based on specific API capabilities.

Field

merchantCustId

Required

Required

Description

Merchant-unique customer identifier. It must be stable and traceable over time.

Field

selfExecute

Required

Required

Description

1 for managed, 2 for self-managed.

Field

mode

Required

Conditionally required

Description

Subscription authorization mode. Credit card subscriptions usually use immediate billing mode.

Managed subscription fields (selfExecute = "1")

Field

frequencyType

Required

Conditionally required

Description

Billing cycle unit, commonly D / M / Y.

Field

frequencyPoint

Required

Conditionally required

Description

Cycle value, used with frequencyType to define billing cadence.

Field

cycleCount / expireDate

Required

One required

Description

Total cycle count or termination date. Provide at least one.

Field

trialDays / trialEnd

Required

Optional (choose one)

Description

Trial duration in days or trial end date.

Field

trialFromPlan

Required

Optional

Description

Whether the trial is counted within the subscription plan cycles.

Field

notificationEmail

Required

Optional

Description

Subscription notification email (recommended).

Field

productName

Required

Optional

Description

Distinguishes multiple subscription plans under the same customer.

Self-managed subscription fields (selfExecute = "2")

Field

frequencyType

Required

Required

Description

Typically configured by day (commonly D).

Field

frequencyPoint

Required

Required

Description

Suggested billing interval value. Actual charges are scheduled by the merchant.

Field

expireDate

Required

Required

Description

Contract termination date. No renewals should be initiated after this date.

Field

productName

Required

Optional

Description

Recommended for distinguishing multiple subscription plans.

Field

bindCard

Required

Optional

Description

If enabled, handle the dual-notification scenario: BIND_CARD + SALE.

subscription must be a JSON string, not an object. Before signing the request, verify escaping and field ordering against your signing implementation requirements.

Initialize the SDK on the frontend (SUBSCRIBE mode) Client-side

const pacypay = new Pacypay(transactionId, {
  container: 'onerway_checkout',
  locale: 'zh-cn',
  environment: 'sandbox',
  mode: 'CARD',
  redirectUrl: redirectUrlFromServer,
  config: {
    subProductType: 'SUBSCRIBE'
  },
  onPaymentCompleted: function (result) {
    const { respCode, respMsg, data } = result || {};
    if (respCode !== '20000') {
      console.error('Subscription request failed:', respMsg);
      return;
    }

    if (data?.status === 'R' && data?.redirectUrl) {
      window.location.replace(data.redirectUrl);
      return;
    }

    if (data?.status === 'S') {
      // For frontend UX feedback only. Use webhook as the final state source of truth.
      console.log('Subscription submitted successfully. Waiting for webhook confirmation.');
    }
  },
  onError: function (error) {
    console.error('SDK error:', error);
  }
});

Handle SDK callbacks and 3DS redirects Client-side Server-side

When the SDK callback returns status = "R":

  1. Read redirectUrl
  2. Redirect immediately to the 3DS verification page
  3. The customer completes verification and returns to returnUrl
  4. The server waits for webhook confirmation of final status
status = "R" only means additional authentication is required. It does not mean failure. Do not close the order or roll back the subscription flow at this stage.

Responsibility boundary (Must / Must not):

RoleMustMust not
ClientShow in-progress state, handle 3DS redirect, and report required logsDo not activate membership or fulfill orders directly from SDK callback
ServerVerify signatures, process idempotently, persist state, and drive business state machineDo not change business state without webhook signature verification

Receive and process webhooks (idempotency required) Server-side

In SDK subscription scenarios, webhooks may include:

  • txnType = "BIND_CARD": card-binding result (typically for credential setup)
  • txnType = "SALE": initial payment or renewal charge result

If subscription-related binding is enabled, the initial stage may return two notifications (BIND_CARD + SALE) and arrival order is not guaranteed.

@PostMapping(value = "/webhook", produces = "text/plain")
public String handleWebhook(@RequestBody Map<String, Object> body) {
    String transactionId = (String) body.get("transactionId");
    String txnType = (String) body.get("txnType");
    String status = (String) body.get("status");

    if (!verifySignature(body)) return "";
    if (alreadyProcessed(transactionId, txnType)) return transactionId;

    if ("BIND_CARD".equals(txnType) && "S".equals(status)) {
        saveTokenId((String) body.get("merchantCustId"), (String) body.get("tokenId"));
    }

    if ("SALE".equals(txnType)) {
        handleSubscriptionSale(body); // Parse scenarios / subscriptionStatus / contractId
    }

    markProcessed(transactionId, txnType);
    return transactionId;
}
Use transactionId + txnType as the idempotency key to avoid collisions between notification types under the same transaction.

Subscription event-to-action mapping

Use webhook field scenarios to identify subscription lifecycle events:

scenarios

SUBSCRIPTION_INITIAL

Meaning

Initial subscription

Suggested business action

Activate service for the first time and persist contractId / tokenId.

scenarios

SUBSCRIPTION_RENEWAL

Meaning

Recurring renewal

Suggested business action

Extend service validity and update next billing time.

scenarios

SUBSCRIPTION_CARD_REPLACEMENT

Meaning

Payment method replacement

Suggested business action

Update credentials while preserving subscription continuity.

scenarios

SUBSCRIPTION_CHANGED

Meaning

Upgrade/downgrade or plan change

Suggested business action

Adjust entitlement, price, or billing cadence.

scenarios

SUBSCRIPTION_CANCELED

Meaning

Subscription canceled

Suggested business action

Stop future renewals and process entitlements by policy.

scenarios

SUBSCRIPTION_ENDED

Meaning

Subscription ended

Suggested business action

End naturally and disable automatic renewals.

Key field and config quick reference

Field

subProductType

Where it appears

Request + SDK config

Purpose

Must be SUBSCRIBE.

Field

subscription

Where it appears

Request

Purpose

Defines subscription rules (JSON string).

Field

transactionId

Where it appears

Response, callback, webhook

Purpose

Transaction identifier and idempotency key.

Field

redirectUrl

Where it appears

Response, SDK callback

Purpose

SDK initialization endpoint or 3DS redirect target.

Field

contractId

Where it appears

SALE notification/result

Purpose

Subscription contract identifier (required for renewal/change operations).

Field

tokenId

Where it appears

BIND_CARD / SALE

Purpose

Reference for follow-up charges or credential updates.

Field

subscriptionManageUrl

Where it appears

Managed mode response/notification

Purpose

Customer self-service subscription management entry.

Field

subscriptionStatus

Where it appears

SALE notification

Purpose

Current subscription status (for example, active, trialing).

Field

scenarios

Where it appears

SALE notification

Purpose

Distinguishes subscription event types.

Wallet subscriptions (Google Pay / Apple Pay)

SDK subscriptions also support wallet payments. Core rules stay the same:

  • Subscription requests still use subProductType = "SUBSCRIBE" (DIRECT for non-subscription transactions)
  • Webhooks remain the transaction source of truth (do not rely on SDK sync callbacks only)
  • Wallet payments usually do not go through traditional 3DS challenges, but you still need failure handling and async notification processing

Prerequisites

  • Join the Google Pay Test Card Suite before testing
  • In H5 WebView scenarios, redirect to the system browser and handle return navigation

Initialization differences

  • Use mode: 'GooglePay'
  • config options include googlePayButtonType, googlePayButtonColor, and googlePayEnvironment
const pacypay = new Pacypay(transactionId, {
  container: 'onerway_checkout',
  locale: 'zh',
  environment: 'sandbox',
  mode: 'GooglePay',
  redirectUrl: redirectUrlFromServer,
  config: {
    subProductType: 'SUBSCRIBE',
    googlePayButtonType: 'subscribe',
    googlePayButtonColor: 'black',
    googlePayEnvironment: 'TEST'
  }
});

Notification identification

Use walletTypeName = "GooglePay" in webhooks and process business logic with the same subscription event flow (scenarios).

This wallet section only highlights subscription-specific differences. For complete parameters and brand guidelines, refer to the dedicated Google Pay and Apple Pay SDK docs.

Test checklist

Best practices

  1. Treat SDK callbacks as frontend UX events, and webhook verification results as business final-state events.
  2. Build a traceable data model for contractId, tokenId, and subscriptionManageUrl, and bind it to merchantCustId.
  3. In managed mode, prioritize notification handling and status synchronization. In self-managed mode, prioritize renewal scheduling and retry strategies.
  4. Enable trials only when product policy is explicit, to avoid conflicts between trial logic and billing logic.
  5. Before launch, at minimum cover success, failure, 3DS, duplicate notifications, out-of-order notifications, and network jitter scenarios.