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:

Feature

Available options

Payment methods

Credit cards (SCA)

Fixed billing cycles

Daily, Weekly, Monthly, Quarterly, Annual

Auto renewal

Free trial period

Setup fee

Configurable amount

Retry logic

Web-hook notifications

List 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:

Name

Type

Description

name

String

Plan name

description

String

Plan description

price

Decimal

Plan price amount

currency

String

Currency standard ISO 4217

periodUOM

Enum string

Defines the billing cycle type (Day, Week, Month, Quarter, Year)

period

Integer

The length of the plan in 'PeriodUOM' units

trialUOM
(Optional)

Enum string

Defines trial period cycle type (Day, Week, Month, Year)

trial Period
(Optional)

Integer

The length of the trial period in 'PeriodUOM' units

setupFee
(Optional)

Decimal

Fee amount that is charged upon subscription creation

isActive

Boolean

Defines if plan is active

isArchived

Boolean

Defines if plan is archived

autoRenewal

Boolean

Defines 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:

Name

Type

Description

planID

Int

The ID of the Plan object this subscription is linked to

customerID

UUID

The ID of the buyer this subscription is linked to

identifier

UUID

Used for grouping renewed subscriptions

status

Enum string

Current subscription status

paymentMethod

Object

Represents the payment method used to pay for this subscription

price

Decimal

Subscription price. Inherited from plan

currency

String

Currency standard ISO 4217. Inherited from plan

periodUOM

Enum string

Defines the billing cycle type (Day, Week, Month, Quarter, Year). Inherited from plan

period

Integer

The length of the plan in 'PeriodUOM' units. Inherited from plan

trialUOM
(Optional)

Enum string

Defines trial period cycle type (Day, Week, Month, Year). Inherited from plan

trialPeriod
(Optional)

Integer

The length of the trial period in 'PeriodUOM' units

setupFee
(Optional)

Decimal

Fee amount that is charged upon subscription creation. Inherited from plan

autoRenewal

Boolean

Defines autorenewal

nextBillingOn

Timestamp

Calculated field, holds the next (in cycle) billing date

endsOn

timestamp

Calculated field, holds the subscription expiration date.

isInBillingRetry

Boolean

Initiated once a payment fails. Means that retry logic is active for this subscription.

nextBillingRetryOn

Timestamp

Next 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.

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:

Parameter

Always Available

Type

Description

action

Yes

plan/created
plan/updated

id

Yes

UUID

Unique plan ID

isActive

Yes

boolean

Defines if plan is active

isArchived

Yes

boolean

Defines if plan is archived

modifiedOn

Yes

Timestamp

Time of last modification

createdOn

Yes

Timestamp

Time of creation

updatedBy

Yes

UUID

Updater Id

createdBy

Yes

UUID

Creator Id

name

Yes

String

Plan name

description

Plan Description

price

Yes

Integer

Plan 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:

Parameter

Always Available

Type

Description

action

Yes

subscription/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.

status

Yes

String

active - Subscription is active
trial - Subscription in trial period
paused - Subscription was paused
expired - Subscription has reached expiration date
cancelled - subscription was cancelled

ID

Yes

UUID

Unique identifier

identifier

Yes

UUID

Used for grouping renewed subscriptions

startedOn

Yes

Timestamp

Indicates when the subscription was created

endsOn

Yes

Timestamp

Indicates when the subscription will expire (if applicable)

autoRenewal

Yes

Boolean

Indicates if the subscription will renew automatically after reaching expiration date

pausedAt

No

Timestamp

Indicates when the subscription was paused

nextBillingOn

No

Timestamp

Indicates when the next payment cycle billing is due (excluding retry billing)

trialStart

No

Timestamp

Trial period start date

trialEnd

No

Timestamp

Trial period end date

nextBillingRetryOn

No

Timestamp

Date and time of next billing retry event (excluding in-cycle billing)

isInBillingRetry

No

boolean

Indicated 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 attempt

Timeline

1

1 day after previous billing attempt

2

7 days after previous billing attempt

3

7 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 Attempt

Attempt type

Billing date

Billing amount

Transaction status

Subscription status

1

Regular billing cycle

Jan 1st

10 GBP

Failed (Soft)

Active

2

Retry #1

Jan 2nd

10 GBP

Failed (Soft)

Active

3

Retry #2

Jan 9th

10 GBP

Failed (Soft)

Active

4

Retry #3

Jan 16th

10 GBP

Failed (Soft)

Active

5

Next billing cycle

Feb 1st

10GBP + 10GBP

Failed (Soft)

Paused