Usage Based Subscriptions Walkthrough

Utilities Service Example

For this example, we will pretend to be a utilities service that want to collect varying amounts of Subscriptions payments from their end users monthly.

  • First, we will need to determine suitable payment methods for our users based on the average basket size. Since the average basket size for billing is in the 30 USD to 50 USD quantum, Direct Debit and Credit Cards will be suitable. We will proceed to apply with Xendit for these channels specifying that autodebit/ merchant initiated transaction is required.
  • While waiting for payment channel activation, we can proceed with testing. To start testing, we will first go to Xendit Dashboard to create an API key (Settings > Developers > API keys) and provide a URL which is hosted on our streaming service servers to receive Subscriptions webhooks from Xendit (Settings > Developers > Callbacks > SUBSCRIPTIONS).
  • Now that the set up is done, we can look at our use case and match it to the features on Xendit APIs
    • Payments are made a fixed date in a month
    • Amount will change every month based on user's usage
    • To maximize payment success rate, we want to perform payment retries when user has balance again
    • Utilities is important to user, we will notify them everytime a attempt at payment fails
    • To maximize payment success rate, we want to notify end users via Whatsapp and emaill when payment fails
    • If user fails to make payment for a month, we will terminate their service
    • There is a base amount that user pays regardless of their utilities usage
  • We can start our integration by linking the payment flows to Xendit's APIs. The first API is Create Customer where we will pass our customer contact information into the request. This helps to store the contact information with Xendit so that the Whatsapp notification can be delivered. We can either provide the information based on user input during registration or create a form for user to input the data.
POST https://api.xendit.co/customers 
{
    "reference_id": "test_reference_id",
    "mobile_number": "+6218181818181",
    "email": "honeybadgey@xendit.co",
    "type" : "INDIVIDUAL",
    "individual_detail": {
        "given_names": "Honey Badgey"
        }
}
  • To simplify our integration, we will use Xendit's UI for creation of payment methods. The next API that we need to hit will be Create Plan.
POST https://api.xendit.co/recurring 
{
  "reference_id": "test_reference_id",
  "customer_id": "cust-239c16f4-866d-43e8-9341-7badafbc019f",
  "recurring_action": "PAYMENT",
  "currency": "IDR",
  "amount": 100000,
  "schedule": {
    "reference_id": "test_reference_id",
    "interval": "MONTH",
    "interval_count": 1,
    "anchor_date": "2022-02-15T16:23:52Z",
    "retry_interval": "DAY",
    "retry_interval_count": 3,
    "total_retry": 3,
    "failed_attempt_notifications": [1,2,3]
  },
  "notification_config": {
    "recurring_created": ["WHATSAPP"],
    "recurring_succeeded": ["WHATSAPP"],
    "recurring_failed": ["WHATSAPP"]
  },
  "failed_cycle_action": "STOP", 
  "metadata": null,
  "success_return_url": "https://www.xendit.co/successisthesumoffailures",
  "failure_return_url": "https://www.xendit.co/failureisthemotherofsuccess"
}

The following parameters solves for these requirements:

ParameterUse case requirements
anchor_datePayments are made a fixed date in a month
interval, interval_countPayments are made monthly
retry_interval, retry_interval_count, total_retryTo maximize payment success rate, we want to perform payment retries when end user has balance again
failed_attempt_notificationsUtilities is important to user, we will notify them everytime a attempt at payment fails
notification_configTo maximize payment success rate, we want to notify end users via Whatsapp when payment fails
failed_cycle_action, failed_attempt_notificationsIf user fails to make payment for a month, we will terminate their service
amountThere is a base amount that user pays regardless of their utilities usage
  • Once the request is sent, we will receive a HTTPS response with a Subscriptions plan object that has status "REQUIRES_ACTION"
{
  "id": "repl-239c16f4-866d-43e8-9341-7badafbc019f",
  "reference_id": "test_reference_id",
  "customer_id": "cust-239c16f4-866d-43e8-9341-7badafbc019f",
  "recurring_action": "PAYMENT",
  "recurring_cycle_count": 0,
  "currency": "IDR",
  "amount": 100000,
  "status": "REQUIRES_ACTION",
  "created": "2020-11-20T16:23:52Z",
  "updated": "2020-11-20T16:23:52Z",
  "schedule_id": "resc-239c16f4-866d-43e8-9341-7badafbc019f",
  "schedule": {
    "reference_id": "test_reference_id",
    "interval": "MONTH",
    "interval_count": 1,
    "created": "2022-02-15T16:23:52Z",
    "updated": "2022-02-15T16:23:52Z",
    "anchor_date": "2022-02-15T16:23:52Z",
    "retry_interval": "DAY",
    "retry_interval_count": 3,
    "total_retry": 3,
    "failed_attempt_notifications": [1,3]
  },
  "immediate_action_type": "FULL_AMOUNT",
  "notification_config": {
    "recurring_created": ["WHATSAPP"],
    "recurring_succeeded": ["WHATSAPP"],
    "recurring_failed": ["WHATSAPP",],
    "locale": "en"
  },
  "failed_cycle_action": "STOP",
  "metadata": null,
  "description": null,
  "items": null,
  "actions": [{
      "action": "AUTH",
      "url": "https://linking-dev.xendit.co/pali_e53e1ca6-3c09-4026-be2e-95ed3d4bb25b",
      "url_type": "WEB",
      "method": "GET"
    }],
  "success_return_url": "https://www.xendit.co/successisthesumoffailures",
  "failure_return_url": "https://www.xendit.co/failureisthemotherofsuccess"
}

The status "REQUIRES_ACTION" indicates that we will need to set our user to be redirected to actions.url for them to complete their payment method account linking. Once our user has been redirected, we will need to listen to webhooks from Xendit to proceed with the next step.

  • If a webhook event for recurring.plan.activated was received, we will match it to the reference_id or id stored in our database to validate that it is a webhook event related to the plan we just created. Once confirmed, we can start providing service to the end user. If recurring.plan.inactivated was received, we can ask the end user to retry the Subscriptions flow.
  • At the same time, a recurring.cycle.created webhook event would also be received for the user's upcoming billing cycle. For usage based Subscriptions, it is necessary to handle and save this Subscriptions cycle information. An example of the webhook is below.
{
  "created": "2022-10-01T12:37:08.251Z",
  "business_id": "62440e322008e87fb29c1fd0",
  "event": "recurring.cycle.created",
  "data": {
    "id": "recy-239c16f4-866d-43e8-9341-7badafbc019f",
    "reference_id": "test_reference_id",
    "customer_id": "cust-239c16f4-866d-43e8-9341-7badafbc019f",
    "recurring_action": "PAYMENT",
    "type": "SCHEDULED",
    "cycle_number": 1,
    "attempt_count": 0,
    "attempt_details": [],
    "status": "SCHEDULED",
    "scheduled_timestamp": "2020-12-20T16:23:52Z",
    "created": "2020-11-20T16:23:52Z",
    "updated": "2020-11-20T16:23:52Z",
    "currency": "IDR",
    "amount": 13579,
    "metadata": null
  },
  "api_version": "v1"
}
  • When it is about time to make deductions for the user's upcoming bill, we will hit the Update Cycle endpoint to include usage cost of our services into the amount to be deducted. Since updates cannot be made once the date component in schduled_timestamp ("2020-12-20", time is disregarded) is reached, we have to ensure that that update is done at least 1 day before schduled_timestamp. This process is then repeated for subsequent months.
  • For each billing month, if payments are successful, a recurring.cycle.succeeded webhook event would be received. If a recurring.cycle.retrying webhook event was received, that means that payment failed but Xendit will still continue trying. If a recurring.cycle.failed webhook event was received, we will terminate the user's service as all payment attempts including retries have failed.

Last Updated on 2023-06-02