> ## Documentation Index
> Fetch the complete documentation index at: https://docs.integrateledger.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Test the complete billing flow

> Verify plan Checkout, a credit-pack top-up, and metered usage in Stripe test mode.

Use a fresh sandbox customer for each run. This walkthrough assumes you created the `api_call` feature (1 credit), `starter` plan (100 credits per month), and `extra_credits` pack (50 credits) in the [quickstart](https://docs.integrateledger.com/quickstart.md). Set `LEDGER_API_KEY` and `LEDGER_API_URL` as described there.

## Before Checkout

In **Developer → Stripe**, connect your Stripe test secret key. In **Developer → Checkout**, set valid success and cancel URLs. In **Catalog → Plans**, confirm Starter shows **Synced** before starting Checkout.

Create a new customer ID that has not been used for a plan Checkout:

```bash theme={null}
export LEDGER_CUSTOMER_ID="sandbox_buyer_$(date +%s)"
curl --fail-with-body --request POST "$LEDGER_API_URL/v1/customers" \
  -H "Authorization: Bearer $LEDGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"customerId\":\"$LEDGER_CUSTOMER_ID\"}"
```

Open this customer in the dashboard. The initial balance should be **0**.

## Complete a plan Checkout

```bash theme={null}
curl --fail-with-body --request POST "$LEDGER_API_URL/v1/billing/attach" \
  -H "Authorization: Bearer $LEDGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"customerId\":\"$LEDGER_CUSTOMER_ID\",\"planId\":\"starter\"}"
```

Open the returned `checkoutUrl` and complete payment using a Stripe test payment method. In **Customers**, wait for the subscription to become **Active** and the balance to reach **100**. The success URL alone is not confirmation: Ledger grants plan credits after a paid invoice webhook.

## Complete a credit-pack top-up

```bash theme={null}
curl --fail-with-body --request POST "$LEDGER_API_URL/v1/billing/attach" \
  -H "Authorization: Bearer $LEDGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"customerId\":\"$LEDGER_CUSTOMER_ID\",\"addOnId\":\"extra_credits\"}"
```

Open this new `checkoutUrl` and complete the one-time test payment. The customer detail should show a **Paid** add-on and **150** available credits. A pack can be purchased without replacing the active monthly plan.

## Check usage and confirm idempotency

```bash theme={null}
curl --fail-with-body --request POST "$LEDGER_API_URL/v1/check" \
  -H "Authorization: Bearer $LEDGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"customerId\":\"$LEDGER_CUSTOMER_ID\",\"featureId\":\"api_call\"}"

curl --fail-with-body --request POST "$LEDGER_API_URL/v1/track" \
  -H "Authorization: Bearer $LEDGER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"customerId\":\"$LEDGER_CUSTOMER_ID\",\"featureId\":\"api_call\",\"request_id\":\"test_action_001\"}"
```

The check should return `allowed: true` and 150 available credits. Track should return `chargedCredits: 1`, `idempotent: false`, and 149 available credits. Send the same track request again: it should return `idempotent: true` and keep the balance at 149. In **Customers → Usage**, you can run the same check and track flow through the dashboard.

## If a balance does not change

Check the customer's subscription or add-on status in **Customers** first. A pending Checkout has not granted credits. Confirm that the Stripe test payment completed and that the webhook reached Ledger. If the plan is not **Synced**, inspect the Stripe connection and save the plan again to retry catalog sync before starting Checkout. Read [Errors and retries](https://docs.integrateledger.com/errors.md) for API error meanings.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.