- Points: 200
- Labels: contract, pay-per-use, token, extensibility
Background
pay_per_use in lib.rs always routes payment to sub.merchant — the merchant stored in the subscription at subscribe time. This means a subscriber cannot make a one-off payment to a different merchant using the same subscription allowance, even if both are whitelisted. Use cases include: tipping a different creator, paying a marketplace fee to a platform address, or routing a single transaction to a sub-merchant in a marketplace context. The contract already tracks the subscription token and validates allowance — the routing logic is the only missing piece.
Task
Add pay_per_use_to(env: Env, user: Address, amount: i128, recipient: Address) as a public function. It follows the same flow as pay_per_use but transfers to recipient instead of sub.merchant. If whitelist::is_whitelist_enabled, verify recipient is whitelisted. Apply the protocol fee (split between fee collector and net to recipient). Record spend against the user's daily limit. Emit a pay_per_use event with recipient in place of sub.merchant. Do NOT update MerchantRevenue for sub.merchant — only update it for recipient.
Key Files
contract/src/lib.rs — add pay_per_use_to; factor shared pay-per-use logic into pay_per_use_inner(env, user, amount, recipient) used by both functions
contract/src/fee.rs — add transfer_pay_per_use(env, user, sub_token, amount, recipient) -> i128 mirroring the subscription charge transfer but accepting an explicit recipient and token
contract/src/test.rs — test: pay_per_use_to transfers to the specified recipient, not sub.merchant; test: whitelist-enabled contract rejects non-whitelisted recipient; test: daily limit applies to pay_per_use_to spending
Edge Cases
- The subscription must be active and not paused (same checks as
pay_per_use)
sub.token is used for the transfer — the recipient cannot specify a different token
pay_per_use_to with recipient == sub.merchant must behave identically to pay_per_use (no special-casing needed, but confirm in a test)
- Daily limit tracks total spend across both
pay_per_use and pay_per_use_to calls
Acceptance Criteria
Background
pay_per_useinlib.rsalways routes payment tosub.merchant— the merchant stored in the subscription atsubscribetime. This means a subscriber cannot make a one-off payment to a different merchant using the same subscription allowance, even if both are whitelisted. Use cases include: tipping a different creator, paying a marketplace fee to a platform address, or routing a single transaction to a sub-merchant in a marketplace context. The contract already tracks the subscription token and validates allowance — the routing logic is the only missing piece.Task
Add
pay_per_use_to(env: Env, user: Address, amount: i128, recipient: Address)as a public function. It follows the same flow aspay_per_usebut transfers torecipientinstead ofsub.merchant. Ifwhitelist::is_whitelist_enabled, verifyrecipientis whitelisted. Apply the protocol fee (split between fee collector and net to recipient). Record spend against the user's daily limit. Emit apay_per_useevent withrecipientin place ofsub.merchant. Do NOT updateMerchantRevenueforsub.merchant— only update it forrecipient.Key Files
contract/src/lib.rs— addpay_per_use_to; factor shared pay-per-use logic intopay_per_use_inner(env, user, amount, recipient)used by both functionscontract/src/fee.rs— addtransfer_pay_per_use(env, user, sub_token, amount, recipient) -> i128mirroring the subscription charge transfer but accepting an explicit recipient and tokencontract/src/test.rs— test:pay_per_use_totransfers to the specified recipient, notsub.merchant; test: whitelist-enabled contract rejects non-whitelisted recipient; test: daily limit applies topay_per_use_tospendingEdge Cases
pay_per_use)sub.tokenis used for the transfer — the recipient cannot specify a different tokenpay_per_use_towithrecipient == sub.merchantmust behave identically topay_per_use(no special-casing needed, but confirm in a test)pay_per_useandpay_per_use_tocallsAcceptance Criteria
pay_per_use_inneris a private function used by bothpay_per_useandpay_per_use_topay_per_use_torequiresuser.require_auth()andensure_contract_not_pausedMerchantRevenueincremented for recipient, notsub.merchantcargo testpasses with no regressions