Build a subscription

Guide

Wire up recurring billing: define a product, price it, charge a customer every month, and let them cancel.

The Model

ProductPriceSubscriptionRenewals

1. Create the product and price

curl -X POST https://api.ching.co.il/v1/products \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Pro Plan" }'
curl -X POST https://api.ching.co.il/v1/prices \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "product": "prod_Lkt0TxOhGNMY",
    "unit_amount": 9900,
    "currency": "ils",
    "tax_mode": "inclusive",
    "type": "recurring",
    "recurring": { "interval": "month" }
  }'

Add a free trial by setting recurring.trial_period_days. Add a yearly option by creating a second price with interval: "year".

2. Subscribe the customer

Assuming the customer has a saved payment method:

curl -X POST https://api.ching.co.il/v1/subscriptions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cus_V8ltq1pK_MWH",
    "payment_method": "pm_WMc9X22NT1af",
    "price": "price_QA8qF3B3VIqE"
  }'

The first period is charged immediately unless the price has a trial. Ching issues a tax invoice, sends a welcome email, and schedules the renewal for one month later.

3. Handle renewals

Ching runs a daily cron that renews subscriptions. Listen for these events:

  • subscription.updated - period dates moved forward, new invoice issued.
  • charge.failed - the renewal charge was declined. The subscription moves to past_due.
  • subscription.canceled - either the customer canceled or enough retries failed.

4. Cancel gracefully

Give customers two flavors of cancel:

# Immediate
curl -X POST https://api.ching.co.il/v1/subscriptions/sub_.../cancel \
  -H "Authorization: Bearer sk_test_..."

# At period end
curl -X POST https://api.ching.co.il/v1/subscriptions/sub_.../cancel \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "cancel_at_period_end": true }'