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

# Trials with prepaid credits

In this example, we'll set up a 30-day trial that ends when either the time limit expires or the customer runs out of credits, whichever comes first. Customers can upgrade to a paid plan at any time by adding a payment method.

The trial works as follows:

* Customers receive 1,000 free credits when they sign up. These credits expire after 30 days.

* The trial ends when the customer runs out of credits or when 30 days have elapsed, whichever comes first.

* Customers can use your product while they have available credits. When credits are depleted, access is blocked.

* Customers are not invoiced during the trial. Invoices only occur after they add a payment method and upgrade to the paid plan.

* When a customer adds a payment method, they immediately upgrade to a \$20/month Pro plan that includes 20 credits per month. Additional credits can be purchased on-demand or via automatic top-ups.

<Tip>
  Use separate plans for trial and paid tiers. This provides clear visibility into trial vs paid customers and ensures credits persist through the upgrade process.
</Tip>

## Core approach

### Two-plan model

Create two separate plans in Orb:

1. **Trial plan**: No recurring charge, no credit allocation. Acts as a marker for trial customers and defines the trial period (30 days).

2. **Pro plan**: \$20/month recurring charge with a 20-credit monthly allocation. This is the paid tier customers upgrade to.

The trial plan does not include a credit allocation. Instead, you grant credits separately using the ledger entry API. This ensures credits persist when customers upgrade from trial to paid.

<Tip>
  Credits granted via plan allocations expire when you change or cancel the subscription. Credits granted via the ledger entry API persist across plan changes and only expire based on their configured expiration date.
</Tip>

Send usage events as customers consume credits and configure your billable metric to draw down from the prepaid credit balance.

## Prerequisites

Before starting a customer on a trial, set up the following:

### Credit balance alerts

Create two alerts for the customer using the [create alert API](/api-reference/alert/create-customer-alert):

1. **`credit_balance_depleted`**: Fires when the customer's credit balance reaches 0
2. **`credit_balance_recovered`**: Fires when credits are added after depletion

Set the currency of these alerts to match your pricing unit (e.g., `credits`). You'll use these webhooks to block and unblock product access.

### Plan creation

Create both the trial plan and Pro plan in Orb:

**Trial plan configuration:**

* No recurring charges
* No credit allocations
* Set a default subscription duration of 30 days (configure this as the plan's default term)
* Set `external_plan_id=trial` for easy reference in your code

**Pro plan configuration:**

* \$20 monthly recurring charge (in-advance)
* 20-credit monthly allocation in `credits` currency
* Credits expire at the end of each month (set this on the allocation)
* Set `external_plan_id=pro`

## Trial setup

### Subscribe customer to trial plan

When a customer signs up, create a subscription to the trial plan:

```python theme={null}
subscription = orb_client.subscriptions.create(
    external_customer_id="customer-18940",
    external_plan_id="trial",
    start_date="2025-03-09T00:00:00Z",
    end_date="2025-04-08T23:59:59Z",  # 30 days from start
    net_terms=0
)
```

Set `end_date` to 30 days from the start date. This schedules automatic cancellation if the customer doesn't upgrade.

### Grant trial credits

After creating the subscription, grant credits using the [create ledger entry](/api-reference/credit/create-ledger-entry-by-external-id) endpoint:

```python theme={null}
orb_client.customers.credits.ledger.create_entry_by_external_id(
    external_customer_id="customer-18940",
    entry_type="increment",
    amount=1000,
    currency="credits",
    per_unit_cost_basis="0.00",  # Free trial credits
    expiry_date="2025-04-08T23:59:59Z"  # Match trial end date
)
```

Set `per_unit_cost_basis` to \$0.00 for trial credits to avoid charges. Set `expiry_date` to match the trial end date.

## Handling credit depletion

### Receive webhook notification

When the customer depletes their credit balance, Orb sends a `customer.credit_balance_depleted` webhook to your endpoint:

```json theme={null}
{
  "type": "customer.credit_balance_depleted",
  "properties": {
    "customer_id": "customer-18940",
    "pricing_unit": {
      "name": "credits"
    }
  }
}
```

### Block product access

Do not block access based solely on the webhook. Webhook delivery order is not guaranteed. The customer may have purchased credits between depletion and webhook delivery.

Instead, query the customer's current balance using the [fetch credit balance](/api-reference/credit/fetch-customer-credit-balance) API when you receive the webhook:

```python theme={null}
balance = orb_client.customers.credits.fetch_by_external_id(
    external_customer_id="customer-18940",
    currency="credits",
    include_all_blocks=False  # Only check currently active blocks
)

if balance["balance"] == 0:
    # Block access in your system
    block_customer_access("customer-18940")
```

If the balance is 0, block access in your application. Update a status field in your database that your product checks before allowing usage.

### End trial early

If the customer has depleted credits and has not added a payment method, end the trial:

```python theme={null}
# Check if customer has payment method
customer = orb_client.customers.fetch_by_external_id(
    external_customer_id="customer-18940"
)

if not customer.get("payment_provider_id"):
    # No payment method, cancel subscription
    orb_client.subscriptions.cancel(
        subscription_id=subscription["id"],
        cancel_option="immediate"
    )
```

## Handling trial expiration

### 30-day limit webhook

When 30 days elapse, Orb automatically cancels the subscription (based on the `end_date` you set). You'll receive a `subscription.ended` webhook:

```json theme={null}
{
  "type": "subscription.ended",
  "subscription": {
    "id": "sub_...",
    "external_customer_id": "customer-18940",
    "status": "ended"
  }
}
```

### Block access on expiration

When you receive this webhook, check if the customer has upgraded to a paid plan:

```python theme={null}
# List customer's active subscriptions
subscriptions = orb_client.subscriptions.list(
    external_customer_id="customer-18940",
    status="active"
)

# Check if customer has an active Pro subscription
has_active_paid_sub = any(
    sub["plan"]["external_plan_id"] == "pro"
    for sub in subscriptions["data"]
)

if not has_active_paid_sub:
    # Trial expired without upgrade, block access
    block_customer_access("customer-18940")
```

## Upgrading to paid plan

When a customer wants to upgrade, collect payment information using Stripe's payment method collection flows. See Stripe's [save and reuse payment methods](https://docs.stripe.com/payments/save-and-reuse) documentation. Once the payment method is saved as the customer's default in Stripe, Orb syncs it automatically.

### Schedule plan change

When the customer adds a payment method, upgrade them immediately to the Pro plan:

```python theme={null}
orb_client.subscriptions.schedule_plan_change(
    subscription_id=subscription["id"],
    external_plan_id="pro",
    change_option="immediate",
    billing_cycle_alignment="plan_change_date"
)
```

Set `change_option=immediate` to upgrade right away. Set `billing_cycle_alignment=plan_change_date` to reset the billing cycle to start today and charge the full \$20 monthly fee immediately.

This creates an invoice for \$20 and grants the customer their first 20-credit monthly allocation.

### Unblock product access

After the plan change completes, unblock access:

```python theme={null}
# Fetch updated subscription
subscription = orb_client.subscriptions.fetch(subscription_id)

if subscription["plan"]["external_plan_id"] == "pro":
    # Customer upgraded, restore access
    unblock_customer_access("customer-18940")
```

### Credit persistence

Credits granted during the trial persist through the upgrade because you granted them via the ledger entry API, not as a plan allocation. The customer keeps any remaining trial credits plus receives their first 20-credit monthly allocation from the Pro plan.

## Preventing double trials

### Check subscription history

Before creating a trial subscription, verify the customer hasn't already had a trial:

```python theme={null}
# Fetch subscription schedule to see plan history
schedule = orb_client.subscriptions.fetch_schedule(
    subscription_id=subscription["id"]
)

# Check if customer was ever on trial plan
had_trial = any(
    phase["plan"]["external_plan_id"] == "trial"
    for phase in schedule
)

if had_trial:
    # Customer already had trial, don't create another
    raise Exception("Customer already used trial")
```

Alternatively, maintain a `had_trial` boolean in your database when you create the first trial subscription.

### Subscription continuity

When a customer upgrades from trial to paid, Orb maintains the same subscription resource. The subscription ID remains constant; only the plan changes. This means:

* A customer can only have one subscription lifecycle
* Plan changes don't reset trial eligibility
* Subscription history provides a complete audit trail

To check if a customer ever had a trial, list their subscriptions and examine the plan history via the fetch schedule endpoint.

## Manual operations

### Extending trials

Extend a trial by updating the subscription's `end_date`:

```python theme={null}
orb_client.subscriptions.update(
    subscription_id=subscription["id"],
    end_date="2025-04-15T23:59:59Z"  # Extend by one week
)
```

If you also want to extend the credit expiration, create a new ledger entry with updated credits or modify the existing block's expiration.

### Compensatory credits

Grant additional credits during the trial without charges:

```python theme={null}
orb_client.customers.credits.ledger.create_entry_by_external_id(
    external_customer_id="customer-18940",
    entry_type="increment",
    amount=500,
    currency="credits",
    per_unit_cost_basis="0.00",
    description="Compensatory credits for service interruption",
    expiry_date="2025-04-08T23:59:59Z"
)
```

Set `per_unit_cost_basis` to \$0.00 to avoid invoicing. Use the `description` field to document the reason for the grant.
