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">
</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";
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.
![Recurring SDK.png 892](https://files.readme.io/19e554a-Recurring_SDK.png)
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 eventssubscription/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 activetrial - Subscription in trial periodpaused - Subscription was pausedexpired - Subscription has reached expiration datecancelled - 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
-
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). -
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. -
Reached the next billing period
If the next billing period is reached, no additional retries will be attempted for that specific failed payment. -
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. -
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 |
Updated 2 months ago