Subscriptions

Introduction

Subscriptions enable businesses that offer ongoing services a way to easily collect recurring payments, helping to generate recurring revenue and reliable cash flow management. Subscriptions represent a set of recurring payments that are linked to a single buyer and authorisation object.

How subscriptions work

Plans and Subscriptions are the main objects needed to set up a successful recurring payments agreement between a buyer and a vendor.

The Plans object defines a single plan, including its price, billing cycle, and charge amount. While the Plans object is active, buyers can subscribe to that plan, creating a Subscriptions object.

The Subscriptions object represents a specific buyer subscription to a specific plan. It holds the subscription start and end dates, as well as information about the plan. The subscriptions object is linked to both a Plan and a buyer. Pausing a subscription will stop the billing cycle from running.

Subscription features

The following is a list of supported subscription features:

FeatureAvailable options
Payment methodsCredit cards (SCA)
Fixed billing cyclesDaily, Weekly, Monthly, Quarterly, Annual
Auto renewal
Free trial period
Setup feeConfigurable amount
Retry logic
Web-hook notificationsList available below

The Plans Object

Each Plans object represents one plan with specific attributes. A plans object can have one of the 3 following statuses:

  • Active - Buyers are free to subscribe to this plan.
  • Paused (or not active) - New buyers cannot subscribe to this plan. Buyers already subscribed to this plan are not affected.
  • Archived - This status is available only when there are no active subscriptions with this plan. Buyers cannot subscribe to archived plans, and they will not show in search lists.

Main plan fields:

NameTypeDescription
nameStringPlan name
descriptionStringPlan description
priceDecimalPlan price amount
currencyStringCurrency standard ISO 4217
periodUOMEnum stringDefines the billing cycle type (Day, Week, Month, Quarter, Year)
periodIntegerThe length of the plan in 'PeriodUOM' units
trialUOM
(Optional)
Enum stringDefines trial period cycle type (Day, Week, Month, Year)
trial Period
(Optional)
IntegerThe length of the trial period in 'PeriodUOM' units
setupFee
(Optional)
DecimalFee amount that is charged upon subscription creation
isActiveBooleanDefines if plan is active
isArchivedBooleanDefines if plan is archived
autoRenewalBooleanDefines autorenewal

The Subscriptions Object

Each Subscriptions object represents a single buyer subscription to a specific plan. A Subscriptions object is created once a buyer is either billed or starts a free trial period. Upon creation, Subscriptions objects inherit attributes from their relevant plan and can have one of the following statuses:

  • Active - Subscription is running each cycle.
  • Trial - Subscription is running, the buyer is currently in trial period.
  • Paused - Subscription is not running, it was paused by the buyer or due to failed attempts to collect payment.
  • Cancelled - Subscription was cancelled and cannot be resumed.
  • Expired - Subscription has reached its expiration date and is now inactive.

Main subscription fields:

NameTypeDescription
planIDIntThe ID of the Plan object this subscription is linked to
customerIDUUIDThe ID of the buyer this subscription is linked to
identifierUUIDUsed for grouping renewed subscriptions
statusEnum stringCurrent subscription status
paymentMethodObjectRepresents the payment method used to pay for this subscription
priceDecimalSubscription price. Inherited from plan
currencyStringCurrency standard ISO 4217. Inherited from plan
periodUOMEnum stringDefines the billing cycle type (Day, Week, Month, Quarter, Year). Inherited from plan
periodIntegerThe length of the plan in 'PeriodUOM' units. Inherited from plan
trialUOM
(Optional)
Enum stringDefines trial period cycle type (Day, Week, Month, Year). Inherited from plan
trialPeriod
(Optional)
IntegerThe length of the trial period in 'PeriodUOM' units
setupFee
(Optional)
DecimalFee amount that is charged upon subscription creation. Inherited from plan
autoRenewalBooleanDefines autorenewal
nextBillingOnTimestampCalculated field, holds the next (in cycle) billing date
endsOntimestampCalculated field, holds the subscription expiration date.
isInBillingRetryBooleanInitiated once a payment fails. Means that retry logic is active for this subscription.
nextBillingRetryOnTimestampNext billing retry date

Features

Auto Renewal
Subscriptions set to renew automatically will do so every time the expiration date is reached. When a subscription renews, a new subscription object is created with the same configuration. All renewed instances of the same subscription will have the same identifier UUID that can be used to track the subscription history.

Setup Fee
Setup fees can be added as an additional cost billed with the first subscription occurrence in a single transaction. The setup fee is defined on the plan level and can only be billed in the same currency as the plan.

Free Trial
Free trial periods allow a subscription to be active for a certain period of time after its creation date without billing the buyer for that period. Trial periods are defined on the plan level, with the period defined by a combination of cycle length (i.e., day, week, month, year) and the number of cycles.

📘

Having both a Setup fee and a Free trial period for a single plan is currently not supported.

Web SDK integration

This section will cover the technical integration of the recurring payments checkout via Web SDK.

Before you begin

  • Make sure you are registered to the UniPaaS portal, you have access to a sandbox account, and the required credentials (PRIVATE_KEY).
  • You have your own checkout page or form to integrate the Web SDK.
  • You have created at least one plan using the Plan operations guide below.

📘

Existing plan

For this tutorial we're going to assume you have created a plan with:
{ "price": 100, "currency": "GBP", "period": 12, "periodUOM": "months" }

And referencing that plan by {planId}

Create Checkout with a Plan
Similar to a single checkout Web SDK integration, you will need to create a checkout on your server.

Make a POST /pay-ins/checkout request as shown below:

curl --location --request POST 'https://sandbox.unipaas.com/platform/pay-ins/checkout' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{PRIVATE_KEY}}' \
--data-raw '{
  "amount": 100,
  "currency": "GBP",
  "country": "GB",
  "email": "[email protected]",
  "plans": [
    {
      "id": {planId}
    }
  ]
}'

Integrate the Web SDK HTML
Similar to the steps described in the single checkout Web SDK integration, you should end up with an html similar to this:

<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.unipaas.com/unipaas.sdk.js"></script>
  <link rel="stylesheet" href="https://cdn.unipaas.com/style.css">
  <!-- polyfills for IE11 users -->
  <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
</head>
<body>
<form class="payment-form">
  <div class="plan--container">
    <div class="plan-name" id="plan-name"></div>
    <div class="plan-price" id="plan-price"></div>
    <div class="plan-trial" id="plan-trial"></div>
  </div>
  <div class="payment-field">
    <label class="payment-field--cardholder-label">
      Cardholder name
      <div class="secure-field--container">
        <div id="holdername"></div>
      </div>
    </label>
  </div>
  <div class="payment-field">
    <label class="payment-field--card-label">
      Card details
      <div id="card"></div>
    </label>
  </div>
  <div class="payment-checkbox">
    <input type="checkbox" name="save-card" />
    <label for="save-card">Save my credit card details securly for future purchases</label>
  </div>
  <button id="submit-form">
    <div class="pay-icon"></div>
    Subscribe
  </button>
  <div class="consent--container">
    <div id="plan-consent"></div>
  </div>
</form>
</body>

<script>
  var unipaas = new Unipaas();

  var SESSION_TOKEN = "TODO: sessionToken";

  // use polyfiils for IE11
  unipaas.usePolyfills();

  unipaas.initTokenize(
    SESSION_TOKEN,
    {
      cardDetails: {
        selector: "#card",
        placeholder: { cardNumber: "1234 5678 9012 3456", cvv: "CVV", expiry: "MM/YY" }
      },
      cardHolder: {
        selector: "#holdername",
        placeholder: "A. Einstein"
      }
    },
    {
      additionalFields: {
        submitButton: {
          selector: "#submit-form"
        }
      },
      mode: "test"
    }
  );

       function disableButtonOnSuccess() {
           var sendButton = window.document.getElementById('submit-form');
           if (sendButton) {
               sendButton.disabled = true;
           }
       }
   
       unipaas.on('onSuccess', function (data) {
           console.log('Success:', data);
           disableButtonOnSuccess(true);
       })
       unipaas.on('onError', function (err) {
           console.log('Error:', err);
       })
</script>

</html>

📘

Additional HTML elements for recurring checkout

Note divs with class: plan--container / consent--container.

plan--container will include the plan details as you set upon plan creation.
consent--container will include a text paragraph that includes consent text.

consent--container is required, and the Web SDK won't be initialized if this div does not present.

The following is an example of a subscription checkout page, including all mandatory Web SDK fields.

892892

Plan operations

Create Plan
Make a POST /pay-ins/plans request to create a new plan:

curl --request POST \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>' \
  --data-raw '{
  "name": "monthly subscription",
  "description": "100 GBP per month, for 12 months",
  "group": "string",
  "price": 100,
  "currency": "GBP",
  "period": 12,
  "periodUOM": "months"
}'

In the response, you'll get the created plan object

Edit Plan
Make a PATCH /pay-ins/plans/{plandId} request to update existing plan:

curl --request PATCH \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>' \
  --data-raw '{
  "name": "monthly subscription - premium",
  "description": "100 GBP per month, for 12 months - premium"
}'

In the response, you'll get the updated plan object.

Get Plan
Make a GET /pay-ins/plans/{planId} request to get existing plan:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the plan object.

Get Plans
Make a GET /pay-ins/plans request to get all existing plans:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get a list of plan object.

Delete Plan
Make a DELETE /pay-ins/plans/{plandId} request to delete existing plan:

curl --request DELETE \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the deleted plan object.

Toggle Plan Activation
Make a POST /pay-ins/plans/{plandId}/activate request to activate existing plan:

curl --request POST \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}/activate' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

Make a POST /pay-ins/plans/{plandId}/deactivate request to deactivate existing plan:

curl --request POST \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}/deactivate' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the updated plan object.

Archive Plan
Make a POST /pay-ins/plans/{plandId}/archive request to archive existing plan:

curl --request POST \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}/archive' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the updated plan object.

Subscription operations

Get Subscriptions by Plan
Make a GET /pay-ins/plans/{planId}/subscriptions request to get existing plan:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}/subscriptions' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get a list of subscription object.

Get Subscription
Make a GET ​/pay-ins​/plans​/{planId}​/subscriptions​/{subscriptionId} request to get existing subscription:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/{planId}/subscriptions/{subscriptionId}' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the subscription object.

Pause Subscription
Make a GET /pay-ins/plans/subscriptions/{subscriptionId}/pause request to pause existing subscription:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/subscriptions/{subscriptionId}/pause' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>' \
  --data-raw '{
  "startDatetime": "2021-01-01 15:30",
  "endDatetime": "2021-01-10 12:30"
}'

In the response, you'll get the updated subscription object.

Resume Subscription
Make a GET /pay-ins/plans/subscriptions/{subscriptionId}/resume request to resume existing subscription:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/subscriptions/{subscriptionId}/resume' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the updated subscription object.

Cancel Subscription
Make a GET /pay-ins/plans/subscriptions/{subscriptionId}/cancel request to cancel existing subscription:

curl --request GET \
  --url 'https://sandbox.unipaas.com/platform/pay-ins/plans/subscriptions/{subscriptionId}/cancel' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PLATFORM_KEY>'

In the response, you'll get the updated subscription object.

Webhook notifications

📘

Before you begin

Please read about how to configure and verify your webhooks.

Subscription webhooks were created to notify about changes that happen regarding your plans or buyer subscriptions.

Plans Object
You will be notified when a new plan is created or updated.

Available webhooks
plan/create , plan/update

The body will include the plans object:

ParameterAlways AvailableTypeDescription
actionYesplan/created
plan/updated
idYesUUIDUnique plan ID
isActiveYesbooleanDefines if plan is active
isArchivedYesbooleanDefines if plan is archived
modifiedOnYesTimestampTime of last modification
createdOnYesTimestampTime of creation
updatedByYesUUIDUpdater Id
createdByYesUUIDCreator Id
nameYesStringPlan name
descriptionPlan Description
priceYesIntegerPlan price amount

Subscriptions object
You will be notified when a new subscription is created or updated, and when a subscription payment is charged or fails.

Available webhooks
subscription/create , subscription/update , subscription/charge-success, subscription/charge-failure

The body will include the subscriptions object:

ParameterAlways AvailableTypeDescription
actionYessubscription/create - notifies about all subscription creation events

subscription/updated - notifies about all changes that made to the subscription, as per the following:
subscription/renewed
subscription/expired
subscription/paused
subscription/paymentOptionExpired

subscription/charge-success notifies about a successful charge that was made by a subscription.

subscription/charge-failure
notifies about a failed charge that was made by a subscription.
statusYesStringactive - Subscription is active
trial - Subscription in trial period
paused - Subscription was paused
expired - Subscription has reached expiration date
cancelled - subscription was cancelled
IDYesUUIDUnique identifier
identifierYesUUIDUsed for grouping renewed subscriptions
startedOnYesTimestampIndicates when the subscription was created
endsOnYesTimestampIndicates when the subscription will expire (if applicable)
autoRenewalYesBooleanIndicates if the subscription will renew automatically after reaching expiration date
pausedAtNoTimestampIndicates when the subscription was paused
nextBillingOnNoTimestampIndicates when the next payment cycle billing is due (excluding retry billing)
trialStartNoTimestampTrial period start date
trialEndNoTimestampTrial period end date
nextBillingRetryOnNoTimestampDate and time of next billing retry event (excluding in-cycle billing)
isInBillingRetryNobooleanIndicated if the subscription is in billing retry mode

Retry logic for failed payments

When a recurring payment fails for a specific subscriber, UniPaaS helps you recover the failed payment and retain that subscriber with an automated billing retry logic.

Our retry logic will engage automatically for some failed payments, excluding the very first payment a customer makes on the spot, when initially subscribing to a plan.

Which failed payments will be retried

Failed payments will be retried based on their last decline code.
Generally, the retry logic will only run if a soft decline was received in the previous billing attempt.
Soft declines indicate that the problem is most likely temporary (e.g. Insufficient funds) and trying to bill the subscriber again might be successful.

When will we stop retrying failed billing attempts

  1. If the previous billing attempt decline type is hard.
    Hard declines indicate that the problem is more permanent, and retrying the billing will not work (e.g. Card marked as lost or stolen).

  2. Reached maximum retry attempts
    The billing retry logic will not try to bill the same payment more than 4 times (including the initial billing attempt). Once the maximum number of retry attempts is reached, we will stop trying to bill that specific payment, and the amount due will stay open.

  3. Reached the next billing period
    If the next billing period is reached, no additional retries will be attempted for that specific failed payment.

  4. Subscription is paused or cancelled
    If the subscription for which the billing retries attempts are made is paused or cancelled, no additional retries will be made for this specific payment.

  5. Successful billing
    In case any of the billing attempts succeeds, no further retries will be attempted for this payment.

Retry schedule
All failed payment retries will follow this schedule:

Retry attemptTimeline
11 day after previous billing attempt
27 days after previous billing attempt
37 days after previous billing attempt

Auto pause subscriptions
Subscriptions will be auto paused when there are 2 consecutive billing cycles that are outstanding, regardless of the number of billing retries made.

When a payment fails, the amount of that payment becomes outstanding until we are able to bill it. In cases where we were not able to bill the outstanding amount before the next billing cycle, the outstanding amount will be added to the next billing cycle amount, and the total amount will be billed in that billing cycle. If that payment fails, the subscription will be paused automatically.

Full retry flow example
This example represents a scenario in which a subscriber has a 10 GBP monthly subscription. On January 1st, one of the payments fails and is not recovered until the subscription is paused:

Billing AttemptAttempt typeBilling dateBilling amountTransaction statusSubscription status
1Regular billing cycleJan 1st10 GBPFailed (Soft)Active
2Retry #1Jan 2nd10 GBPFailed (Soft)Active
3Retry #2Jan 9th10 GBPFailed (Soft)Active
4Retry #3Jan 16th10 GBPFailed (Soft)Active
5Next billing cycleFeb 1st10GBP + 10GBPFailed (Soft)Paused