Subscription Tutorial
Payload’s Billing Schedule feature allows you to easily create and manage customer subscriptions.
How Does it Work
- Create new customers and plans for new subscribers
- Allow customers to add/manage their billing method securely
- Sit back and let Payload’s automatic billing trigger the payments
- Get real-time notifications of payments or declines with webhooks
Get Started
If you don’t already have at least one dedicated Processing Account for your business, you’ll need to set one up, otherwise jump to Setup a New Subscription.
Adding a Processing Account
Add a Processing Account
<script src="https://payload.co/Payload.js"
pl-client-key="client_key_AWcpDnNBB7oLfNqfQ6g66262"
pl-btn-open="processing-account">
</script>
<script>
var pl = Payload('client_key_AWcpDnNBB7oLfNqfQ6g66262')
new pl.ProcessingAccount()
.on('account_created', function(evt) {
console.log(evt.account)
})
</script>
Response
{}
Every payment needs to be associated with an active Processing Account to process. Processing Accounts specify the pricing and settings for how payments should be processed, including what funding account to transmit the payment to.
Processing Accounts can be added through the dashboard or the full setup process can be integrated into your platform using the Integrated Processing Account Setup toolkit.
To integrate the processing account setup into your app or platform, you can use the Processing Account UI toolkit to capture the details for a new processing account.
Below is an example of the Processing Account form. Click the button to view.
Setup a New Subscription
1) Add a Customer
Create a New Customer
curl "https://api.payload.co/customers/" \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d email="matt.perez@example.com" \
-d full_name="Matt Perez" \
-d primary_processing_id="processing_account_id" \
-d phone_number="1234567890"
account = pl.Customer.create(
full_name='Full name',
email='email@example.com',
phone_number='(111) 111-1111',
attrs={
"custom_attribute": "Custom input"
},
primary_processing_id='processing_account_id'
)
pl.Customer.create({
full_name: 'Full name',
email: 'email@example.com',
phone_number: '(111) 111-1111',
attrs: {
"custom_attribute": "Custom input"
},
primary_processing_id: 'processing_account_id'
}).then(function(account) {
// Handle account creation
})
Response
{}
When a user signs up, create a new Customer using one of Payload’s SDKs.
2) Add Payment Methods
Add Payment Methods
pl.PaymentMethod.create({
type: card,
card: {
card_number: "numeric",
expiry: "mm/yy"
},
account_id: customer_id
}).then(function(card){
// Handle card creation
})
payment = pl.PaymentMethod.create(
type='card',
card={
'card_number': 'numeric',
'expiry': 'mm/yy'
},
account_id='customer_id'
)
curl https://api.payload.co/payment_methods \
--header "Content-Type: application/json" \
-u test_client_key_3bW9JMZtPVDOfFNzwRdfE: \
-d '{"type":"card","card":{"card_number":"numeric", "expiry":"mm/yy"},"account_id":"customer_id"}'
Secure Input Form Example (Bootstrap 4)
<form id="checkout-form" class="container" pl-form="payment">
<input type="hidden" pl-input="amount" value="10.00">
<div class="row pt-2">
<div class="form-group col-7 px-1">
<label>Card Number</label>
<div class="form-control" pl-input="card_number"></div>
</div>
<div class="form-group col-3 px-1">
<label>Expiration</label>
<div class="form-control" pl-input="expiry"></div>
</div>
<div class="form-group col-2 px-1">
<label>CVC</label>
<div class="form-control" pl-input="cvc"></div>
</div>
</div>
<div class="row pt-2">
<button class="btn btn-primary" type="submit">Pay Now</button>
</div>
</form>
Import Payload
<script src="https://payload.co/Payload.js"></script>
Initialize the Form
<script>
var pl = Payload('UPDATE WITH YOUR test_client_key_....')
var checkout_form = new pl.Form({
form: $('#checkout-form').get(0)
})
</script>
Response
{}
Once the Customer account has been created, you can add Payment Methods programmatically, or use Payload’s Secure Input feature to securely capture card or bank account information from any HTML form.
Cards
Bank Accounts
3) Create a Billing Schedule
Create Billing Schedule
curl "https://api.payload.co/billing_schedules/" \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d start_date="2019-01-01" \
-d end_date="2019-12-31" \
-d recurring_frequency="monthly" \
-d type="subscription" \
-d processing_id="acct_3bW9JMapnT7sw7neax7ui" \
-d charges[0][type]="supreme" \
-d charges[0][amount]=19.99 \
-d customer_id="acct_3bW9JMorTV1q6mXkkXUTg"
billing_schedule = pl.BillingSchedule.create(
start_date=datetime.date(2019, 1, 1),
end_date=datetime.date(2019, 12, 31),
type='subscription',
processing_id='acct_3bW9JMapnT7sw7neax7ui',
recurring_frequency='monthly',
charges=[
pl.BillingCharge(type='supreme', amount=19.99)
],
customer_id='acct_3bW9JMorTV1q6mXkkXUTg'
)
pl.BillingSchedule.create({
start_date: '2019-01-01',
end_date: '2019-12-31',
type: 'subscription',
processing_id: 'acct_3bW9JMapnT7sw7neax7ui',
recurring_frequency: 'monthly',
charges: [
new pl.BillingCharge({
type: 'supreme',
amount: 19.99
})
],
customer_id: 'acct_3bW9JMorTV1q6mXkkXUTg'
}).then(function(billing_schedule) {
// Handle billing schedule creation
})
Response
{}
Now that the Customer has stored payment information, it’s easy to create a customized billing schedule based on your pricing plans.
Trigger & Track Payments
Your billing schedule will generate an Invoice every period (based on its recurring_frequency
). You can either set up automatic billing or trigger those payments manually.
Automatic Billing
Update Default Billing Method
curl "https://api.payload.co/payment_methods/pm_3bW9JMoHcb0u4ZmM7FZ32" \
-X PUT \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d default_payment_method=true
payment_method.update(
default_payment_method=True
)
customer.update({
default_payment_method: true
}).then(function(customer) {
// Handle customer update
})
To enable automatic billing, update the default_payment_method
flag to true
on the Customer’s desired payment method object. The Customer’s default Payment Method will be automatically charged on the due date of any invoice that gets generated by the Billing Schedule.
Webhooks
Create a Webhook
curl "https://api.payload.co/webhooks/" \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d trigger="automatic_payment" \
-d url="https://api.your-app.com/webhooks-endpoint"
webhook = pl.Webhooks.create(
trigger='automatic_payment',
url='https://api.your-app.com/webhooks-endpoint'
)
pl.Webhooks.create({
trigger: 'payment',
url: 'https://api.your-app.com/webhooks-endpoint'
}).then(function(webhook) {
// Handle webhook creation
})
Response
{}
Use Payload’s webhooks to watch for successful and unsuccessful subscription payments. Some useful triggers are: automatic_payment
, payment
, bank_account_reject
, and decline
. For a full list of webhook triggers, see the Object Reference.
Pay an Invoice Manually
Pay an Invoice
curl -s "https://api.payload.co/transactions" \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE:\
-X POST \
-d customer_id=acct_3bW9JND5iKnAlOYn9u82q \
-d amount=100 \
-d type=payment \
-d allocations[0][invoice_id]=inv_3bW9JND5iKnAlOYn9u82q
invoice = pl.Invoice.get('inv_3bW9JND5iKnAlOYn9u82q')
if invoice.status != 'paid':
payment = pl.Payment.create(
amount=invoice.amount_due,
customer_id=invoice.customer_id,
allocations=[
pl.PaymentItem( invoice_id=invoice.id )
]
))
pl.Invoice.get('inv_3bW9JND5iKnAlOYn9u82q')
.then(function( invoice ){
if ( invoice.status !== 'paid' ) {
pl.Payment.create({
amount: invoice.amount_due,
customer_id: invoice.customer_id,
allocations: [
new pl.PaymentItem({
invoice_id: invoice.id,
})
]
})
}
})
Invoices can be accessed through the Billing Schedule object’s invoices
field, or at the API endpoints https://api.payload.co/billing_schedules/{id}/invoices
or https://api.payload.co/invoices/{id}
.
Once you’ve found the unpaid Invoice, pay it by creating a payment object with a nested payment line item within the allocations nested array. This will allocate the transaction to the invoice, subtracting from any balance due on that invoice.
Manage Existing Subscriptions
Thanks to Payload’s intuitive, object-oriented design, it’s easy to manage subscriptions using update
operations on the Billing Schedule and Billing Charge objects.
Cancel a Subscription
Update Billing Schedule End Date
curl "https://api.payload.co/billing_schedule/{id}" \
-X PUT \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d end_date="yyyy-mm-dd"
billing_schedule.update(
end_date='yyyy-mm-dd'
)
billing_schedule.update({
end_date: 'yyyy-mm-dd'
}).then(function(update) {
// Handle billing schedule update
})
Response
{}
To cancel a subscription, simply update the Billing Schedule’s end_date
. After the end_date
is reached, no additional Invoices will be generated.
Upgrade a Subscription
Update a Billing Charge
curl "https://api.payload.co/billing_charge/{id}" \
-X PUT \
-u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
-d amount="19.99" \
-d type="supreme"
billing_charge.update(
type='supreme',
amount=19.99
)
billing_charge.update({
type: 'supreme',
amount: 19.99
}).then(function(update) {
// Handle billing schedule update
})
Response
{}
To upgrade a subscription, update
the appropriate Billing Charge object in the charges
array.
Query Subscriptions
Query your Billing Schedules, Payments, Customers, and any other object associated with your subscriptions using our powerful query language with built-in aggregate response value modifier functions and group by capabilities.
With our software libraries’ ARM (API Relational Model) design, constructing complex reporting queries is simple and intuitive.
Example Queries
Find Unpaid Invoices
results = pl.Invoice.select(
pl.attr.amount_due.sum()
pl.attr.status,
pl.attr.type
).filter_by(
pl.attr.status == 'overdue'
).group_by(
pl.attr.customer_id
).all()
pl.Invoice.select(
pl.attr.amount_due.sum(),
pl.attr.status,
pl.attr.customer_id
).filter_by(
pl.attr.status.eq('overdue')
).group_by(
pl.attr.customer_id
).then(function(results) {
// Handle query results
})
customer_id | status | sum(amount_due) |
---|---|---|
acct_3btsXsJQrwdf6FD0FnWU7 | overdue | 59.94 |
acct_3btxqcWaQOhSb8jRk3cm7 | overdue | 29.97 |
acct_3bvLzBP92YQhkRjusD195 | overdue | 119.88 |
acct_3bvM06IcMoIklRmC8yfmy | overdue | 89.91 |
Count Billing Charges by type
results = pl.BillingCharge.select(
pl.attr.id.count(),
pl.attr.type,
pl.attr.created_at.year()
).filter_by(
pl.attr.created_at.year() == 2019
).group_by(
pl.attr.type
).all()
pl.BillingCharge.select(
pl.attr.id.count(),
pl.attr.type,
pl.attr.created_at.year()
).filter_by(
pl.attr.created_at.year().eq(2019)
).group_by(
pl.attr.type
).then(function(results) {
// Handle query results
})
count(id) | type | year(created_at) |
---|---|---|
1244 | basic | 2019 |
687 | premium | 2019 |
322 | supreme | 2019 |