> ## Documentation Index
> Fetch the complete documentation index at: https://vowena-dependabot-github-actions-actions-checkout-7.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> The subscription lifecycle on Vowena - creation, state machine, status transitions, allowance calculations, and the one-signature authorization model.

## What is a Subscription?

A subscription is the on-chain record linking a **subscriber** to a **plan**. It tracks billing state, lifecycle status, and timing. Each subscription is its own persistent ledger entry, enabling parallel processing across the network.

***

## State Machine

Every subscription moves through a defined set of states. Transitions are enforced by the contract - there is no way to skip a state or force an invalid transition.

| From       | To            | Trigger                                                                     |
| ---------- | ------------- | --------------------------------------------------------------------------- |
| -          | **Active**    | `subscribe()` called                                                        |
| **Active** | **Paused**    | Charge fails and grace period expires                                       |
| **Active** | **Expired**   | `max_periods` reached during charge                                         |
| **Active** | **Cancelled** | Subscriber calls `cancel()`                                                 |
| **Paused** | **Active**    | Subscriber calls `reactivate()` (funds now available)                       |
| **Paused** | **Cancelled** | One more billing period passes while paused, or subscriber calls `cancel()` |

<Info>
  **Paused** is a recoverable state. If a subscriber tops up their wallet and
  calls `reactivate()`, the subscription returns to Active and billing resumes.
  But if another full billing period passes while paused, the next `charge()`
  call will transition it to Cancelled permanently.
</Info>

<Note>
  There is no transition from **Cancelled** or **Expired** back to any other
  state. These are terminal. The subscriber must create a new subscription if
  they want to resume.
</Note>

***

## Creating a Subscription

When a subscriber calls `subscribe()`, several things happen in a single atomic transaction:

<Steps>
  <Step title="Subscriber authorization">
    The subscriber signs the transaction. Soroban's **auth tree** means the subscriber signs **once** - this single signature covers both the `subscribe()` contract call and the `token.approve()` call nested inside it. No separate approval transaction needed.
  </Step>

  <Step title="Plan validation">
    The contract loads the plan and verifies:

    * The plan exists
    * The plan is `active == true`
    * The subscriber is not the merchant (cannot subscribe to your own plan)
  </Step>

  <Step title="Allowance calculation">
    The contract calculates how much token allowance to request:

    ```
    allowance = price_ceiling * effective_periods
    ```

    Where `effective_periods` is:

    * `max_periods` if the plan has a limit, or
    * `120` if the plan is unlimited (`max_periods == 0`)

    The allowance expiry is set to the **maximum Soroban allows**: approximately 6,312,000 ledgers (\~347 days at 5-second block times).
  </Step>

  <Step title="Token approval">
    The contract calls `token.approve(subscriber, contract_address, allowance, expiry_ledger)`. This grants the Vowena contract permission to pull up to `allowance` tokens from the subscriber over the approval period.
  </Step>

  <Step title="Subscription stored">
    The contract increments `NextSubId`, creates the `Subscription` struct with `status: Active`, and writes it to persistent storage. The subscription ID is appended to both `SubscriberSubs(subscriber)` and `PlanSubs(plan_id)` indexes.
  </Step>

  <Step title="Event emitted">
    A `SubscriptionCreated` event fires with the full subscription struct.
  </Step>
</Steps>

<Warning>
  The allowance is based on `price_ceiling`, not the current `amount`. This is
  intentional - it ensures that if the merchant raises the price (within the
  ceiling), existing subscribers do not need to re-authorize. The subscriber
  sees the ceiling amount in their wallet's approval prompt.
</Warning>

***

## Allowance Calculation Examples

<Tabs>
  <Tab title="12-month plan">
    **Plan:** 10 USDC/month, ceiling 15 USDC, max 12 periods

    ```
    allowance = 15 USDC * 12 = 180 USDC
    expiry    = current_ledger + 6,312,000 (~347 days)
    ```

    The subscriber's wallet will show: "Approve 180 USDC for Vowena contract".
  </Tab>

  <Tab title="Unlimited plan">
    **Plan:** 5 USDC/month, ceiling 8 USDC, max 0 (unlimited)

    ```
    allowance = 8 USDC * 120 = 960 USDC
    expiry    = current_ledger + 6,312,000 (~347 days)
    ```

    After the allowance expires (\~347 days), the subscriber would need to re-approve. The contract handles this gracefully - a charge will fail, the subscriber can re-approve and reactivate.
  </Tab>

  <Tab title="Plan with trial">
    **Plan:** 20 USDC/month, ceiling 25 USDC, max 12, trial 2 periods

    ```
    allowance = 25 USDC * 12 = 300 USDC
    ```

    The allowance covers all 12 periods including trials. During the 2 trial periods, `charge()` advances the counter but does not transfer tokens. The allowance is not consumed during trials.
  </Tab>
</Tabs>

***

## Cancellation

Cancellation is always available to the subscriber and is immediate:

```typescript theme={null}
const tx = await client.cancel({
  subscriber: subscriberKeypair.publicKey(),
  sub_id: 42n,
});
```

<AccordionGroup>
  <Accordion title="Who can cancel?">
    Only the subscriber can cancel their own subscription. The contract calls
    `subscriber.require_auth()`. Merchants cannot cancel on behalf of
    subscribers.
  </Accordion>

  <Accordion title="What happens to the allowance?">
    The remaining token allowance stays until it expires naturally on the
    ledger. The contract will never use it after cancellation because the
    subscription status is `Cancelled` and `charge()` checks status before
    attempting any transfer.
  </Accordion>

  <Accordion title="Is it refundable?">
    Cancellation does not trigger an automatic refund. The current period is
    already paid. Merchants can issue voluntary [refunds](/protocol/refunds)
    separately.
  </Accordion>
</AccordionGroup>

***

## Reactivation

If a subscription is **Paused** (charge failed, grace expired), the subscriber can reactivate it:

<Steps>
  <Step title="Top up wallet">
    The subscriber ensures they have sufficient token balance and that the
    contract's allowance has not expired.
  </Step>

  <Step title="Call reactivate">
    The subscriber calls `reactivate(sub_id)`. The contract verifies the
    subscription is in `Paused` status and transitions it back to `Active`.
  </Step>

  <Step title="Billing resumes">
    The `next_billing_time` is reset so that the next `charge()` call can
    process immediately.
  </Step>
</Steps>

<Tip>
  If the original token allowance has expired while the subscription was paused,
  the subscriber will need to create a new subscription instead. Reactivation
  only works if the allowance is still valid.
</Tip>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Billing" icon="credit-card" href="/protocol/billing">
    The most important page - understand exactly what happens during a charge.
  </Card>

  <Card title="Migrations" icon="arrow-right-arrow-left" href="/protocol/migrations">
    How merchants move subscribers to new plans with explicit consent.
  </Card>
</CardGroup>
