Sell prepaid credit
Let customers buy credit ahead of time that pays for usage before anything is charged. Charge the pack with an invoice and add the credit when it is paid.
In this tutorial a customer buys a 20 USD credit pack from your app. Your server charges it to the card they already use for their subscription. When the payment goes through, a webhook adds 20 USD of credit to their account in Comecero. When usage is billed, the credit pays first and only the rest is charged.
Before you start
- An agreement with a metered entitlement, for example
agr_team_42from Bill for API calls. - A test secret token in
COMECERO_SECRET_TOKEN, used only on your server. - A webhook endpoint that checks signatures, as in the webhooks guide.
Give the customer a credit balance
Credit lives in a credit entitlement of the agreement. It has no metric and no price of its own.
curl https://api.comecero.com/api/v1/usage/entitlements \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entitlement_id": "credit_team_42",
"kind": "credit",
"billing_agreement_id": "agr_team_42"
}'Then point the metered entitlement at it with funding_source_entitlement_id. Posting an entitlement with the same id updates it:
curl https://api.comecero.com/api/v1/usage/entitlements \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entitlement_id": "ent_team_42_api_calls",
"billing_agreement_id": "agr_team_42",
"metric_id": "met_api_calls",
"rate_plan_id": "api_calls_standard",
"rate_plan_version": 1,
"funding_source_entitlement_id": "credit_team_42"
}'Credit is in the agreement's currency. A balance starts at 0.
Charge the credit pack
When the customer clicks Buy 20 USD of credit, your server makes an invoice for the pack and pays it with the customer's saved payment method. meta says which credit to add, so the webhook knows what to do.
const API = 'https://api.comecero.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.COMECERO_SECRET_TOKEN}`, 'Content-Type': 'application/json' };
async function call(method, path, body) {
const response = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
const answer = await response.json();
if (!response.ok) throw new Error(`${method} ${path}: ${response.status} ${answer.error?.message ?? ''}`);
return answer;
}
export async function buyCredit({ comeceroCustomerId, creditEntitlementId, amount }) {
const invoice = await call('POST', '/invoices', {
customer_id: comeceroCustomerId,
currency: 'USD',
draft: false,
suppress_customer_notification: true,
items: [{ name: `Prepaid credit, ${amount} USD`, type: 'service', quantity: 1, reference_price: amount }],
meta: { credit_entitlement_id: creditEntitlementId, credit_amount: amount },
});
const methods = await call('GET', `/customers/${encodeURIComponent(comeceroCustomerId)}/payment_methods`);
const method = methods.data.find((m) => m.is_default) ?? methods.data[0];
if (!method) throw new Error('no saved payment method: send the customer an invoice link instead');
return call('POST', `/invoices/${encodeURIComponent(invoice.invoice_id)}/payments`, { payment_method_id: method.payment_method_id });
}comeceroCustomerId is the Comecero customer who pays the subscription. It is not your own id. Read it once from Get a subscription with expand=customer, and store it next to the agreement.
If the payment fails, tell the customer and let them try again. Do not retry it on your own: a retry can charge twice. Without a saved payment method, create a payment link and send the customer to it.
Add the credit when it is paid
Subscribe to invoice:paid (as in the webhooks guide). When an invoice with your meta is paid, add the credit. Use the invoice id in idempotency_key: a webhook sent twice then still adds the credit once.
app.post('/webhooks/comecero', express.raw({ type: 'application/json' }), async (req, res) => {
if (!isFromComecero(req.body, req.get('x-comecero-signature'))) return res.sendStatus(401);
const event = JSON.parse(req.body);
const invoice = event.data;
if (event.type === 'invoice:paid' && invoice.meta?.credit_entitlement_id) {
const answer = await fetch(
`https://api.comecero.com/api/v1/usage/entitlements/${encodeURIComponent(invoice.meta.credit_entitlement_id)}/credits`,
{
method: 'POST',
headers: { Authorization: `Bearer ${process.env.COMECERO_SECRET_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: invoice.meta.credit_amount,
idempotency_key: `invoice_${invoice.invoice_id}`,
description: `Credit pack, invoice ${invoice.invoice_id}`,
}),
},
);
if (!answer.ok) return res.sendStatus(500); // Comecero sends the event again later
}
res.sendStatus(200);
});The credit amount comes from meta.credit_amount, not from the invoice total, so tax on the pack never changes how much credit the customer gets.
The answer says "added": true the first time and "added": false for a repeat, with the balance either way.
Show the balance
curl https://api.comecero.com/api/v1/usage/entitlements/credit_team_42/balance \
-H "Authorization: Bearer $COMECERO_SECRET_TOKEN"{ "object": "entitlement_balance", "entitlement_id": "credit_team_42", "balance": 20, "entries": 1, "billing_region": "us" }In the dashboard, the customer's page under Usage Billing, Customers shows Prepaid credit, with Add credit for a credit you give by hand, for example after an outage.
How the credit is used
Credit is used when a period is billed, at the subscription's renewal, not as each event arrives. For each metered entitlement, Comecero takes the included allowance first, then pays the price of the rest from the credit, and charges only what is left.
With the API calls price from Bill for API calls and 130,000 calls, the usage is 220.00 USD. The 20 USD credit pays 20.00, and 200.00 USD goes on the renewal invoice. The balance is then 0.
The usage meter keeps counting at a zero balance. If your plan should stop when the credit runs out, check the balance against usage so far in your app and stop the service yourself.
Next steps
- Credits and burndown has the full rules, with several entitlements on one credit.
- Add prepaid credit in the API reference.
Bill for seats
Charge for the most seats a team used in a month with a gauge metric. Send a seat count every hour, include five seats and price the rest per seat.
Show usage in your app
Show each customer how much of their plan they have used this month, and email them at 80% with the usage threshold webhook. Uses usage so far and alerts.