NAV Navbar
Logo

Subscription Tutorial

Payload’s Billing Schedule feature allows you to easily create and manage customer subscriptions.

How Does it Work

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/accounts/" \
  -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" \
  -d type="customer"
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="3bW9JMapnT7sw7neax7ui" \
  -d charges[0][type]="supreme" \
  -d charges[0][amount]=19.99 \
  -d customer_id="3bW9JMorTV1q6mXkkXUTg"
billing_schedule = pl.BillingSchedule.create(
    start_date=datetime.date(2019, 1, 1),
    end_date=datetime.date(2019, 12, 31),
    type='subscription',
    processing_id='3bW9JMapnT7sw7neax7ui',
    recurring_frequency='monthly',
    charges=[
        pl.BillingCharge(type='supreme', amount=19.99)
    ],
    customer_id='3bW9JMorTV1q6mXkkXUTg'
)
pl.BillingSchedule.create({
    start_date: '2019-01-01',
    end_date: '2019-12-31',
    type: 'subscription',
    processing_id: '3bW9JMapnT7sw7neax7ui',
    recurring_frequency: 'monthly',
    charges: [
        new pl.BillingCharge({
            type: 'supreme',
            amount: 19.99
        })
    ],
    customer_id: '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/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 account_id=3bW9JND5iKnAlOYn9u82q \
    -d amount=100 \
    -d type=payment \
    -d allocations[0][invoice_id]=3bW9JND5iKnAlOYn9u82q
invoice = pl.Invoice.get('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('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)
3btsXsJQrwdf6FD0FnWU7 overdue 59.94
3btxqcWaQOhSb8jRk3cm7 overdue 29.97
3bvLzBP92YQhkRjusD195 overdue 119.88
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