Shell Python Node

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="generated_client_key"
  pl-btn-open="processing-account">
</script>
<script>
var pl = Payload('generated_client_token'); // See UI Authentication on how to obtain a client token
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="[email protected]" \
  -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 protected]',
    phone_number='(111) 111-1111',
    attrs={
      "custom_attribute": "Custom input"
    },
    primary_processing_id='processing_account_id'
)
const customer = await pl.Customer.create({
    full_name: 'Full name',
    email: '[email protected]',
    phone_number: '(111) 111-1111',
    attrs: {
      "custom_attribute": "Custom input"
    },
    primary_processing_id: 'processing_account_id'
})

Response

{}

When a user signs up, create a new Customer using one of Payload's SDKs.


2) Add Payment Methods

Add Payment Methods

const card = await pl.PaymentMethod.create({
    type: card,
    card: {
      card_number: "numeric",
      expiry: "mm/yy"
    },
    account_id: customer_id
})
card = 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 generated_client_token: \
  -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('generated_client_token')

  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'
)
const billing_schedule = await 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'
})

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 = await customer.update({
  default_payment_method: true
})

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'
)
const webhook = await pl.Webhooks.create({
    trigger: 'payment',
    url: 'https://api.your-app.com/webhooks-endpoint'
})

Response

{}

Use Payload's webhooks to watch for successful and unsuccessful subscription payments. Some useful triggers are: automatic_payment, payment, 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 )
        ]
    ))
const invoice = await pl.Invoice.get('inv_3bW9JND5iKnAlOYn9u82q')

if (invoice.status !== 'paid') {
  const payment = await 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'
)
const update = await billing_schedule.update({ end_date: 'yyyy-mm-dd' })

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
)
const update = await billing_charge.update({
    type: 'supreme',
    amount: 19.99
})

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()
const results = await pl.Invoice
  .select(
    pl.Invoice.amount_due.sum(),
    pl.Invoice.status,
    pl.Invoice.customer_id
  )
  .filterBy(pl.Invoice.status.eq('overdue'))
  .groupBy(pl.Invoice.customer_id)
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()
const results = await pl.BillingCharge
  .select(
    pl.BillingCharge.id.count(),
    pl.BillingCharge.type,
    pl.BillingCharge.created_at.year()
  )
  .filterBy(pl.BillingCharge.created_at.year().eq(2019))
  .groupBy(pl.BillingCharge.type)
count(id) type year(created_at)
1244 basic 2019
687 premium 2019
322 supreme 2019