| title | charge |
|---|---|
| description | Charges a due subscription by transferring tokens from subscriber to merchant. Permissionless. Anyone can call this function to trigger billing. |
fn charge(env: Env, sub_id: u64) -> boolAttempts to charge a subscription that is due for billing. This is the only write function in Vowena that requires no authorization. Anyone can call it. The contract validates all conditions on-chain and either transfers funds or handles the failure gracefully.
Returns true if the charge succeeded, false if it did not.
| Name | Type | Description |
|---|---|---|
sub_id |
u64 |
The subscription ID to charge. |
None. This function has no require_auth(). Anyone can call it.
bool - true if the charge was processed successfully, false if it was not charged (e.g., not yet due, insufficient funds).
Before attempting the transfer_from, the contract checks the subscriber's balance and allowance. This is a deliberate design decision:
The contract checks, in order:
- Does the subscription exist and is it
Active? - Has the
next_billing_timepassed? - Is this a trial period? (If so, advance the period counter without transferring funds.)
- Has
max_periodsbeen reached? (If so, expire the subscription.) - Does the subscriber have sufficient balance?
- Does the subscriber have sufficient allowance?
- Execute
transfer_from()to move funds from subscriber to merchant.
| Event | Topics | Data | Condition |
|---|---|---|---|
charge_ok |
subscriber, sub_id, amount |
Period number | Charge succeeded |
charge_fail |
subscriber, sub_id |
Failure reason | Balance or allowance insufficient |
sub_paused |
subscriber, sub_id |
failed_at timestamp |
Grace period expired on a previously failed charge |
sub_expired |
subscriber, sub_id |
periods_billed |
Max periods reached |
sub_cancel |
subscriber, sub_id |
cancelled_at timestamp |
Subscription cancelled due to expiry |
charge() is designed to fail gracefully rather than revert. It returns false for most failure conditions rather than throwing an error. However, the following will cause a revert:
| Code | Name | Description |
|---|---|---|
| 8 | SubNotFound |
No subscription exists with the given sub_id. |
```typescript import { VowenaClient, NETWORKS } from "@vowena/sdk";
const client = new VowenaClient({
contractId: NETWORKS.testnet.contractId,
rpcUrl: NETWORKS.testnet.rpcUrl,
networkPassphrase: NETWORKS.testnet.networkPassphrase,
});
// Anyone can build and submit this. No specific signer required
const tx = await client.buildCharge(
"GCALLER...ADDR", // Any address (pays the tx fee)
subscriptionId // Subscription ID to charge
);
const signedXdr = await signTransaction(tx);
const result = await client.submitTransaction(signedXdr);
console.log("Charged:", result.charged); // true or false
```
- **Keeper bots** - automated services that monitor `next_billing_time` and call `charge()` on schedule.
- **The merchant** - can call `charge()` directly from their backend.
- **The Vowena Dashboard** - includes a built-in keeper service.
- **Third-party networks** - any service can integrate charge calling.
The caller pays the Stellar transaction fee (~0.00001 XLM), but needs no special authorization.
1. The `charge_fail` event is emitted.
2. `failed_at` is set to the current timestamp.
3. On the **next** `charge()` call, if `now > failed_at + grace_period`, the subscription is paused.
4. A paused subscription can be reactivated by the subscriber via [`reactivate()`](/api-reference/reactivate).