Skip to main content
This guide walks you through the complete integration flow for recurring payments using QFPay’s Subscription API.

Understanding the two-step process

When using recurring payments, the process involves two separate steps:

1. Token creation (after 3DS authentication)

After the customer completes 3DS via qfpay.confirmPayment() on the frontend, the system creates a payment token. This step verifies the cardholder’s identity and stores the payment method securely for future billing.

2. Subscription creation

The token_id generated in step 1 is then used to call the API /subscription/v1/create, which creates the subscription and schedules recurring billing.

Why you see two transaction records

Because these two steps serve different purposes, you will normally see two transaction records:
  • One for token creation / payment verification
  • One for subscription billing
This is part of the normal recurring payment lifecycle. The system requires the tokenisation step before subscriptions can be created.

Billing options

Depending on your business logic, you can implement the first charge in two different ways:

Option 1 — Verification charge + first subscription charge

Example: subscription HKD 100 / month starting January Token creation: HKD 1 verification charge (Jan)
  • Used only for identity verification and tokenisation.
Subscription creation:
  • First billing HKD 100 in January
  • Subsequent charges HKD 100 every month.
This approach is typically used when you want to separate verification from the actual billing.

Option 2 — First subscription charge during token creation

Example: subscription HKD 100 / month starting January Token creation: HKD 100
  • This charge serves both as identity verification and the first subscription payment.
Subscription creation:
  • Billing starts from February onwards (HKD 100 monthly).
This approach avoids a small verification charge and uses the first payment as part of the subscription.

Prerequisites

Before you begin, ensure you have:
  • A QFPay merchant account with recurring payment features enabled
  • API credentials (app_code and API key)
  • A web application with HTTPS enabled
  • Basic understanding of REST APIs and webhooks

Integration steps

Step 1: Create customer

Create a customer record to associate with subscriptions.
POST /subscription/v1/customer
Request:
{
  "customer_name": "John Doe",
  "customer_email": "john@example.com",
  "customer_phone": "+85212345678"
}
Response:
{
  "customer_id": "cus_abc123",
  "customer_name": "John Doe",
  "customer_email": "john@example.com"
}

Step 2: Create product

Define the subscription product with pricing and billing cycle.
POST /subscription/v1/product
Request:
{
  "product_name": "Premium Plan",
  "product_description": "Monthly premium subscription",
  "amount": 10000,
  "currency": "HKD",
  "billing_cycle": "monthly"
}
Response:
{
  "product_id": "prod_xyz789",
  "product_name": "Premium Plan",
  "amount": 10000,
  "currency": "HKD"
}

Step 3: Render payment element (frontend)

Initialize the payment element on your checkout page.
const payment = qfpay.payment({
  customer_id: 'cus_abc123',
  amount: 10000,
  currency: 'HKD',
  save_card: true
});

payment.mount('#payment-element');

Step 4: Confirm payment and create token

After customer enters card details, confirm the payment to trigger 3DS and create token.
const result = await qfpay.confirmPayment({
  payment,
  return_url: 'https://yoursite.com/payment/complete'
});

if (result.token_id) {
  // Send token_id to your backend
  await fetch('/api/create-subscription', {
    method: 'POST',
    body: JSON.stringify({
      token_id: result.token_id,
      customer_id: 'cus_abc123',
      product_id: 'prod_xyz789'
    })
  });
}

Step 5: Create subscription (backend)

Use the token to create the subscription.
POST /subscription/v1/create
Request:
{
  "customer_id": "cus_abc123",
  "product_id": "prod_xyz789",
  "token_id": "tok_def456",
  "start_date": "2024-01-01"
}
Response:
{
  "subscription_id": "sub_ghi789",
  "status": "active",
  "next_billing_date": "2024-02-01"
}

Step 6: Handle webhooks

Set up webhook endpoints to receive subscription events.
app.post('/webhooks/subscription', (req, res) => {
  const event = req.body;
  
  switch(event.type) {
    case 'subscription.payment_succeeded':
      // Handle successful payment
      break;
    case 'subscription.payment_failed':
      // Handle failed payment
      break;
    case 'subscription.cancelled':
      // Handle cancellation
      break;
  }
  
  res.status(200).send('OK');
});

Testing

Use test card numbers in sandbox environment:
  • Success: 4111 1111 1111 1111
  • 3DS Required: 4000 0027 6000 3184
  • Declined: 4000 0000 0000 0002

Best practices

  1. Always validate webhooks - Verify webhook signatures to ensure authenticity
  2. Handle failed payments gracefully - Implement retry logic and notify customers
  3. Store subscription IDs - Keep subscription_id in your database for management
  4. Test thoroughly - Test all scenarios including failures and cancellations
  5. Monitor subscription health - Track payment success rates and churn

Next steps