Recurring charges

The Pismo platform's Recurring charges API enables you to set up recurring charge plans for accounts. For example, you could use it to set up an annual fee.

To set up a plan for an account, you first create the plan, then create a link between the plan and the account. Once the two are linked, the plan is automatically activated, and the account can begin incurring charges based on the plan settings. One plan can be linked to multiple accounts, and one account can be linked to multiple plans.

📘

An account can even be linked to the same plan multiple times, as long as each link has a different tracking_id.

You can deactivate a plan for a specific account by deleting the link between them, or you can disable the plan altogether.

These processes are explained in more detail in the following sections.

Recurring charge plans

Create a plan

To create a recurring charge plan, use the Create recurring charge plan endpoint. If successful, this endpoint returns an ID for the recurring charge plan, which can be used with the other endpoints in the API.

A recurring charge plan is defined on the organization (Org) level.

Disable a plan

To disable a recurring charge plan, use the Disable recurring charge plan endpoint. Once a plan is disabled, it can no longer be linked to accounts. If you set disable_all_accounts to true, the endpoint also deletes any existing links associated with the plan, and the accounts associated with the deleted links no longer incur the charges specified in the plan.

If disable_all_accounts is false (the default), then accounts that were linked to the plan before it was disabled remain linked to it, and the accounts continue to incur charges based on the plan.

Recurring charge links

Create a link

To link a recurring charge plan to an account, use the Create link endpoint. If successful, this endpoint returns an ID for the link.

Delete a link

To deactivate a plan that's linked to an account, use the Delete recurring charge link endpoint. If successful, the link between the plan and the account is deleted, and the account no longer incurs the charges specified in the plan. This does not delete the plan itself, and any links the plan has with other accounts are unaffected.

Recurring scheduled charges

Recurring scheduled charges are recurring charges with status PENDING. In other words, they are charges that are scheduled, but have not yet been incurred by the account.

View scheduled charges

Use the List scheduled charges endpoint to retrieve a list of scheduled charges filtered by query parameters. The returned list includes the ID for each charge in the list. Use the Get scheduled charge endpoint with this ID to retrieve the information for a specific charge.

Update a scheduled charge

Use the Update scheduled charge endpoint to modify a scheduled charge.

📘

Update scheduled charge does not change the recurring charge plan, which might be linked to multiple accounts. It updates one charge for one cycle/statement of one account.

Examples for recurring charge plans

Example 1

Suppose you define a recurring charge plan with the following settings:

  • number_of_cycles = 6
  • installment_amount = 20
  • first_cycles_to_discount = 2
  • discount_percentage = 10
  • split_transaction = true

Since split_transaction is true, two transactions are posted to the account for each discounted installment — a debit transaction for the installment amount and a credit transaction for the discount. (If an installment is not discounted, only one transaction is posted.)

📘

You specify the type of transaction used for the installment using processing_code. If split_transaction is true, you specify the type of transaction used for the discount using secondary_processing_code. So, normally, you would use processing_code to specify a debit transaction and secondary_processing_code to specify a credit transaction. However, you aren’t forced to do this. You could make both transactions debits, for example. It’s unlikely that you’ll encounter a scenario where you would need to do something like this, so it's not recommended.

installment_amount is the charge per cycle before any discount is applied. Since the discount percentage is 10%, the actual discount is $2 (the installment amount $20 multiplied by 0.1). first_cycles_to_discount is 2, so the charge is $18 ($20 - $2) for the first two cycles and $20 for the next four cycles.

What happens after cycle 6 depends on what you specify for renew_method. There are three possible settings:

renew_methodDescription
NO_RENEWThe link does not renew after number_of_cycles have passed. In the example, no more charges are incurred after cycle 6 as a result of the plan.
WITH_DISCOUNTThe link is renewed after the specified number of cycles have passed, and the discount is applied again.

In the example, the discount is applied to cycles 1 and 2, but no discount is applied to cycles 3 through 6. After the 6th cycle, the link between the plan and the account is deleted, a new link (with a new link ID) is created, and the sequence repeats, with the same discounts applied to cycles 1 and 2. This sequence of events continues to repeat indefinitely.
WITHOUT_DISCOUNTThe link is renewed after number_of_cycles have passed, but the discount is not applied.

In the example, after the 6th cycle, the link between the plan and the account is deleted and a new link is created. In this case, however, the full charge of $20 is incurred for all 6 cycles (assuming the account balance does not drop below minimum_spend_to_charge). The link continues to be recreated every 6 cycles with the full charge incurred for each cycle.

Suppose you set renew_method to WITH_DISCOUNT. You create the plan using a request similar to this:

curl --request POST \
     --url https://sandbox.pismolabs.io/recurring-charge/v1/recurring-charge-plans \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'x-cid: 5bb05174-4e80-11ea-b77f-2e728ce88125' \
     --data '
{
  "split_transaction": true,
  "description": "Annuity {counter}",
  "secondary_description": "Discount {counter}",
  "number_of_cycles": 6,
  "first_cycles_to_discount": 2,
  "discount_percentage": 10,
  "minimum_spend_to_charge": 100,
  "renew_method": "WITH_DISCOUNT",
  "tracking_id": "3ea22692-a209-11eb-89a2-f73e179dcbc2",
  "processing_code": "99066",
  "secondary_processing_code": "99067",
  "installment_amount": 20
}

Notice that minimum_spend_to_charge is set to $100. This is the minimum amount of debt (total debits) that the account must have for charges to be incurred. Assume the account balance does not drop below this amount. With these settings, the client incurs the following charges on their statements:

Cycle numberInstallment amountDiscountCharge
1$20$2$18
2$20$2$18
3$20$2$20
4$20$2$20
5$20$2$20
6$20$2$20

Since split_transaction is true, two transactions — a debit transaction for the base amount of the installment (the amount before the discount is applied) and a credit transaction for the discount amount — are posted to the account for cycles 1 and 2. So, for example, on the statement for cycle 1, there would be a debit transaction in the amount of $20 and a credit transaction for $2. Both transactions would appear on the statement.

Since renew_method is set to WITH_DISCOUNT, the same charges repeat in the next 6 cycles and the next 6, and so on.

Example 2

Suppose you define a recurring charge plan with the same settings as in Example 1, but with split_transaction set to false:

  • number_of_cycles = 6
  • installment_amount = 20
  • first_cycles_to_discount = 2
  • discount_percentage = 10
  • split_transaction = false
  • renew_method = WITH_DISCOUNT

You create the plan using a request similar to this:

curl --request POST \
     --url https://sandbox.pismolabs.io/recurring-charge/v1/recurring-charge-plans \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'x-cid: 5bb05174-4e80-11ea-b77f-2e728ce88125' \
     --data '
{
  "split_transaction": false,
  "description": "Annuity {counter}",
  "number_of_cycles": 6,
  "first_cycles_to_discount": 2,
  "discount_percentage": 10,
  "minimum_spend_to_charge": 100,
  "renew_method": "WITH_DISCOUNT",
  "tracking_id": "3ea22692-a209-11eb-89a2-f73e179dcbc2",
  "processing_code": "99066",
  "installment_amount": 20
}

The charges incurred are the same as in example 1, however, only one transaction is created for each installment. For cycle 1, for example, there would be one debit transaction for $18 posted to the account. The installment would also appear as one transaction on the statement.

Example 3

Suppose you want to charge an annual fee of $120. There are two ways to do this:

  • You could split the charge across the cycles:
    number_of_cycles = 12
    installment_amount = 10
  • You could set installment_amount to 120 and discount the first 11 cycles by 100%. This would result in no charges for the first 11 cycles and a charge of $120 in cycle 12:
    number_of_cycles = 12
    installment_amount = 120
    first_cycles_to_discount = 11
    discount_percentage = 100

Examples for scheduled charges

Example 1

When you use List scheduled charges or Get scheduled charge to view scheduled charges, you must take into account the value of split_transaction. If split_transaction is true, then the installment_amount field returned by these endpoints has the same value that you entered when you created the plan, and the secondary_installment_amount field represents the value of the discount (if any) that's applied to installment_amount. See example 1 in the last section.

If split_transaction is false, then the discount (if any) is deducted from installment_amount and saved back into the same field. In other words, installment_amount is set equal to installment_amount minus the discount. So, the new installment amount can be different depending on whether the charge is discounted.

The following code shows the kind of results you might get from running List scheduled charges. In this case, only one charge is listed because per_page is set to 1. There is a value for secondary_installment_amount, so the plan probably has split_transaction set to true, although this could have been overridden using Update scheduled charge. (See next example.)

{
  "current_page": 1,
  "per_page": 1,
  "pages": 10,
  "total_items": 10,
  "is_last_page": true,
  "items": [
    {
      "recurring_scheduled_charge_id": 880077,
      "org_id": "TN-cc8f8b89-233a-4582-9f36-63ee85278d6d",
      "account_id": 6912345,
      "created_at": "2023-03-30T12:26:41",
      "updated_at": "2022-02-01",
      "statement_id": 9497889,
      "recurring_charge_link_id": 98660282,
      "status": "PENDING",
      "creation_cid": "f7ad1742-cae5-4d59-be97-7d6127b363ea",
      "cancellation_cid": "10a30b55-53a0-4cf9-9e20-25a6ccec12dd",
      "processing_code": "99066",
      "installment_amount": 10.9,
      "description": "Annuity 1/12",
      "authorization_id": 1099901,
      "authorization_tracking_id": "fd0e5a8a-bc33-436a-8fba-0e82c7d7c12e",
      "secondary_processing_code": "99067",
      "secondary_installment_amount": 5.45,
      "secondary_description": "Discount",
      "secondary_authorization_id": 1099901,
      "secondary_authorization_tracking_id": "7ec3b2b2-8e70-4b0a-ac4d-86c6275310c2"
    }
  ]
}

📘

If you supply a value in the description field when you create a link, this value is used in the description field of all the charges, as in the above example. If you don't provide a value for description in the link, the description field of the plan is used.

The secondary_description field in the charge always gets its value from the plan.

Example 2

When you use Update scheduled charge, it's possible to split the charge between installment_amount and secondary_installment_amount, even if split_transaction is false. However, to do this, you need to enter a value for secondary_processing_code. Otherwise, you get an error.

Suppose you have a recurring charge plan with the following settings:

  • number_of_cycles = 6
  • installment_amount = 20
  • first_cycles_to_discount = 2
  • discount_percentage = 50
  • processing_code = "99066"
  • split_transaction = false

The plan is applied to an account with ID 3512347. When you run List scheduled charges on this account, the result includes the following information for one of the scheduled charges:

  "items": [
    ...
    {
      "recurring_scheduled_charge_id": 450066,
      "org_id": "TN-cc8f8b89-233a-4582-9f36-63ee85278d6d",
      "account_id": 3512347,
      "created_at": "2023-02-12T11:19:11",
      "updated_at": "2023-02-20",
      "statement_id": 9497889,
      "recurring_charge_link_id": 12560211,
      "status": "PENDING",
      "creation_cid": "22bccfdc-e282-4b33-afa4-56e9dd30783b",
      "cancellation_cid": "e55ca816-9b9a-424a-a251-9ff76b042030",
      "processing_code": "99066",
      "installment_amount": 10,
      "description": "Annuity 3/6",
      "authorization_id": 2059211,
      "authorization_tracking_id": "294f167e-dd0f-4e75-b430-8361d8ba8ebe",
    },
    ...
  ]

Notice that installment_amount is $10, which is 50% less than the value of installment_amount that was used to create the plan. This is because split_transaction is set to false. The value of installment_amount returned by List scheduled charges is the discounted amount.

Now, suppose you execute the following request:

curl --request PATCH \
     --url https://sandbox.pismolabs.io/recurring-charge/v1/accounts/3512347/scheduled-charges/450066 \
     --header 'accept: application/json' \
     --header 'authorization: Bearer eyJhbGciOiJIUzUxMiIsImtpZCI6Ijk1MzA0YzRjZTAxN2I3NTg3YTFlMGQzMjVjZmVkOTVkZDY3ZTYzNTUifQ.eyJpc3MiOiJQYXNzcG9ydCIsInN1YiI6IkFVVEgtMjc4ZWRmNzktZjQ2MS00ZDAwLTkxNDEtZmYxZWZhZjNhNTEwIiwiZXhwIjoxNjk0NjI0ODc5LCJpYXQiOjE2OTQ2MjQyNzksImFpZCI6IjEyOTc3NjQyMyIsInVpZCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNDo2NzgxOTM3MTY2OGE2MmRjMWIxMjE4YTQ4NGE4YzQ1NTE2NWU0OTkwIiwiYWNjb3VudC1pZCI6IjEyOTc3NjQyMyIsInRrdmVyIjoiMiIsInRlbmFudF9pZCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNCIsInRlbmFudCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNCIsInRva2VuX3R5cGUiOiJjbGllbnRfY3JlZGVudGlhbHMifQ.pIon_X9Y5Su1kKPNvPTRf4pkUxTbuo-nX7Yp-LCl3FTBjmekWQuxbrr0rv6yW0iN8E5DSV83GuSETXK8Bfj7JA' \
     --header 'content-type: application/json' \
     --data '
{
  "installment_amount": 20,
  "processing_code": "99066",
  "description": "Annuity 3/6",
  "secondary_processing_code": "99067",
  "secondary_installment_amount": 10,
  "secondary_description": "Discount"
}

This request updates the scheduled charge shown in the previous code block. It changes installment_amount to 20 and sets secondary_installment_amount to 10. Since split_transaction is set to false in the plan, you had to include a value for secondary_processing_code. Assume that "990066" and "990067" are processing codes for a debit transaction and a credit transaction, respectively. Then, the amount of the charge is 20 - 10 = $10 — the same as it was before you executed the request. You've just split the transaction for this one charge.

Example 3

Update scheduled charge can be used to settle a charge. Suppose a client asks the bank to fully discount a customer's installment for a particular cycle. To skip the installment, you execute the endpoint with installment_amount set to 0 and use the statement ID to specify the cycle you want to skip.

Say you have the same recurring charge plan that was used in the previous example:

  • number_of_cycles = 6
  • installment_amount = 20
  • first_cycles_to_discount = 2
  • discount_percentage = 50
  • processing_code = "99066"
  • split_transaction = false

The plan is applied to an account with ID 3512347. You run List scheduled charges to find the ID for the statement that you want to skip, which turns out to be the one shown below (the same one as in the last example):

  "items": [
    ...
    {
      "recurring_scheduled_charge_id": 450066,
      "org_id": "TN-cc8f8b89-233a-4582-9f36-63ee85278d6d",
      "account_id": 3512347,
      "created_at": "2023-02-12T11:19:11",
      "updated_at": "2023-02-20",
      "statement_id": 9497889,
      "recurring_charge_link_id": 12560211,
      "status": "PENDING",
      "creation_cid": "22bccfdc-e282-4b33-afa4-56e9dd30783b",
      "cancellation_cid": "e55ca816-9b9a-424a-a251-9ff76b042030",
      "processing_code": "99066",
      "installment_amount": 10,
      "description": "Link between an account and a recurring charge plan",
      "authorization_id": 2059211,
      "authorization_tracking_id": "294f167e-dd0f-4e75-b430-8361d8ba8ebe",
    },
    ...
  ]

To skip the charge for this statement's cycle, you execute the following request:

curl --request PATCH \
     --url https://sandbox.pismolabs.io/recurring-charge/v1/accounts/3512347/scheduled-charges/450066 \
     --header 'accept: application/json' \
     --header 'authorization: Bearer eyJhbGciOiJIUzUxMiIsImtpZCI6Ijk1MzA0YzRjZTAxN2I3NTg3YTFlMGQzMjVjZmVkOTVkZDY3ZTYzNTUifQ.eyJpc3MiOiJQYXNzcG9ydCIsInN1YiI6IkFVVEgtMjc4ZWRmNzktZjQ2MS00ZDAwLTkxNDEtZmYxZWZhZjNhNTEwIiwiZXhwIjoxNjk0NjI0ODc5LCJpYXQiOjE2OTQ2MjQyNzksImFpZCI6IjEyOTc3NjQyMyIsInVpZCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNDo2NzgxOTM3MTY2OGE2MmRjMWIxMjE4YTQ4NGE4YzQ1NTE2NWU0OTkwIiwiYWNjb3VudC1pZCI6IjEyOTc3NjQyMyIsInRrdmVyIjoiMiIsInRlbmFudF9pZCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNCIsInRlbmFudCI6IlROLTZhZWM2MTNmLTQ0YmEtNDE4NC05NDg4LWJmMDM0M2U3NDczNCIsInRva2VuX3R5cGUiOiJjbGllbnRfY3JlZGVudGlhbHMifQ.pIon_X9Y5Su1kKPNvPTRf4pkUxTbuo-nX7Yp-LCl3FTBjmekWQuxbrr0rv6yW0iN8E5DSV83GuSETXK8Bfj7JA' \
     --header 'content-type: application/json' \
     --data '
{
  "installment_amount": 0,
  "processing_code": "99066",
  "description": "Installment settled",
}