VietBilling
All guides
VietBilling Engineering Guides

Subscription billing architecture for SaaS products on PayOS

Design Customer, Checkout, Payment, Subscription, and entitlement flows without treating a browser redirect as proof of payment.

VietBilling EngineeringVerified 2026-08-3110 min read

The real problem starts after the payment link

PayOS provides payment links, transaction status, and webhooks. A SaaS product must also know which plan a customer bought, when the current period ends, whether a payment starts or renews a subscription, and when the application may grant access.

Your app → Customer → Product + ProductPrice
         → Checkout → PayOS payment link
         ← verified Payment ← PayOS webhook/reconciliation
         → Order → Subscription → BenefitGrant
         → signed VietBilling webhook → entitlement in your app

A redirect to success_url is a browser experience, not payment evidence. The user can close the tab, alter the URL, or return before the webhook arrives.

Keep five concepts separate

  1. Customer maps your SaaS user to a billing identity.
  2. Product and ProductPrice describe what is sold and its price at purchase time.
  3. Checkout represents an attempt that can time out or be retried.
  4. Payment and Order record money movement and the completed commercial obligation.
  5. Subscription carries state across periods; entitlement is the result your app applies.

Using one payments table for everything leaves basic questions unanswered: should a failed payment disable an existing subscription, should a price change rewrite history, and can a retry create two subscriptions?

Create Checkout safely

Create the Customer once, store customer_id, and send a stable idempotency key with product_id:

curl -X POST "https://api.vietbilling.com/public/v2/checkouts" \
  -H "Authorization: Bearer vb_live_..." \
  -H "Idempotency-Key: checkout_order_123" \
  -H "Content-Type: application/json" \
  -d '{
    "external_customer_id": "user_42",
    "customer_email": "[email protected]",
    "product_id": "PRODUCT_UUID",
    "success_url": "https://app.example/success",
    "cancel_url": "https://app.example/cancel"
  }'

Redirect to the returned checkout_url. After a timeout, retry with the same key or retrieve the Checkout instead of creating another logical order.

Grant access from trusted state

Grant entitlement only after a valid VietBilling subscription webhook or an explicit Public API reconciliation confirms completion. Process events idempotently by event_id and subscription_id. The browser result page should show a pending state and query your own backend.

Design for failure first

If the architecture handles these cases, the happy path usually follows naturally.

Verification source

Check payment-link payloads, signatures, and statuses against the current PayOS API documentation before production changes. VietBilling is independent software and does not replace the merchant's PayOS account.