Skip to main content

How Refunds Work

Vowena supports on-chain refunds initiated by the merchant. When a merchant issues a refund, they are transferring their own funds back to the subscriber. The contract never holds funds - refunds are a direct merchant-to-subscriber transfer recorded on-chain.
Refunds in Vowena are voluntary. The protocol does not force refunds on cancellation. Merchants decide when and how much to refund based on their own policies. The protocol provides the mechanism and the verifiable receipt.

Refund Flow

1

Merchant calls refund

The merchant calls refund(sub_id, amount) with the subscription ID and the amount to refund.
2

Authorization

The contract calls merchant.require_auth() - confirming the caller is the plan’s merchant. Only the merchant who owns the plan can issue refunds for subscriptions on that plan.
3

Token transfer

The contract calls token.transfer(merchant, subscriber, amount). This is a direct transfer from the merchant’s wallet to the subscriber’s wallet. The contract acts as the executor but never holds the tokens.
4

Event emitted

A RefundIssued event is emitted with sub_id, plan_id, and amount. This creates a permanent, verifiable on-chain receipt of the refund.
The merchant must have sufficient token balance to cover the refund. If the merchant’s wallet does not have enough tokens, the transaction will revert. The contract does not escrow funds for potential refunds.

Partial Refunds

Refunds support any amount - there is no requirement to refund the full billing amount. Common scenarios:
ScenarioRefund AmountExample
Full period refundplan.amountSubscriber cancels mid-cycle, merchant refunds the last charge
Partial refundAny i128 > 0Pro-rated refund for unused days in a billing period
Goodwill creditAny i128 > 0Merchant issues a credit for service disruption
Multi-period refundplan.amount * NMerchant refunds multiple past periods
There is no on-chain enforcement of refund limits. A merchant could refund more than was ever charged. The amount parameter accepts any positive value. Business logic around refund policies should be handled at the application layer.

Code Examples

import { VowenaClient } from "vowena";

const client = new VowenaClient({ contractId, networkPassphrase, rpcUrl });

// Full refund of one period (10 USDC with 7 decimals)
const tx = await client.refund({
  merchant: merchantKeypair.publicKey(),
  sub_id: 42n,
  amount: 10_0000000n,
});

// Partial refund (3.50 USDC)
const partialTx = await client.refund({
  merchant: merchantKeypair.publicKey(),
  sub_id: 42n,
  amount: 3_5000000n,
});

Verifiable Receipts

Every refund emits a RefundIssued event that serves as a permanent, publicly verifiable receipt:
{
  "type": "RefundIssued",
  "topics": ["sub_id: 42", "plan_id: 1"],
  "data": {
    "amount": 100000000
  }
}

For subscribers

The refund is visible in their wallet’s transaction history and can be independently verified by querying the Stellar ledger. No “refund pending” ambiguity.

For merchants

The on-chain event serves as an immutable accounting record. Integrate with the SDK event listener to sync refunds to your accounting system.

Key Properties

Issuing a refund does not cancel, pause, or otherwise change the subscription. The subscription continues in whatever state it was in. If the merchant wants to cancel after refunding, they should instruct the subscriber to call cancel().
Once the token transfer executes, it is final. There is no “undo refund” function. This is a direct on-chain transfer with immediate settlement.
A merchant can issue multiple refunds for the same subscription. Each one is a separate transaction with its own event. There is no lifetime refund cap enforced by the contract.

What’s Next

Security

Understand the full security model and consumer protection guarantees.

Billing

See how charges work and why the pre-check mechanism matters.