Subscriptions: Intent + Stripe and Tempo Implementations#230
Subscriptions: Intent + Stripe and Tempo Implementations#230brendanjryan wants to merge 16 commits into
Conversation
Spec Preview
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62334f6e06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| | Field | Type | Required | Description | | ||
| |-------|------|----------|-------------| | ||
| | `methodDetails.chainId` | number | OPTIONAL | Tempo chain ID. If omitted, the default value is 4217 (Tempo mainnet). | |
There was a problem hiding this comment.
Align default Tempo chain ID across method specs
Set methodDetails.chainId default to the same value used by the other Tempo method drafts (specs/methods/tempo/draft-tempo-charge-00.md:154 and specs/methods/tempo/draft-tempo-session-00.md:543 both use 42431). Keeping 4217 here means clients that omit chainId will derive signatures and did:pkh identifiers on a different chain than existing Tempo payment flows, which can cause valid subscription credentials to fail verification or be charged on the wrong network.
Useful? React with 👍 / 👎.
…on guidance - Remove placeholder review notes from intent and tempo method specs - Add T3 network upgrade requirement for TIP-1011 features - Rename 'Tempo Network' to 'Tempo' in ASCII diagrams - Add Access Key Isolation section: servers SHOULD use one key per subscription for fault isolation; documents risks of key reuse including shared TokenLimit, wider blast radius, and bulk revocation
| date: 2024-06 | ||
| --- | ||
|
|
||
| --- abstract |
There was a problem hiding this comment.
I'm curious if there is a doc on the motivation? Eg, why build subscriptions into the tempo layer vs utilize a billing system and then create payment on Tempo? Not a statement of judgement, just trying to understand the product goals.
| | `amount` | string | Fixed payment amount per billing period in base units | | ||
| | `currency` | string | Currency or asset identifier (see {{currency-formats}}) | | ||
| | `periodSeconds` | string | Billing period duration in seconds | | ||
| | `subscriptionExpires` | string | Subscription expiry timestamp in {{RFC3339}} format | |
There was a problem hiding this comment.
why does a subscription need to expire? Eg, if you sign up for a service like Netflix there is not expiration
There was a problem hiding this comment.
that's fair -- I think we could have this be optional and only have a firm expiry if desired
alternative we could remove and add later as an optional field to reduce API surface
| |-------|------|-------------| | ||
| | `amount` | string | Fixed payment amount per billing period in base units | | ||
| | `currency` | string | Currency or asset identifier (see {{currency-formats}}) | | ||
| | `periodSeconds` | string | Billing period duration in seconds | |
There was a problem hiding this comment.
what is the start date? Often subscriptions do not start now() but anchored to something like UTC midnight.
There was a problem hiding this comment.
this is defined later -- but start date should be immediately, this could have some variability at the payment method (e..g stripe, tempo) level though
|
|
||
| # Terminology | ||
|
|
||
| Subscription |
There was a problem hiding this comment.
This is a fairly narrow definition, that does not match the billing perspective, which might be fine, but who is the audience for this concept and would they be confused?
One other thought - given the big definition difference between this subscription and a billing subscription - should we consider another name (maybe 'recurring')?
There was a problem hiding this comment.
Recurring is interesting -- that may be more semantically correct as this is a charge x interval. Will think about this a bit more
| `methodDetails`, but MUST define exact activation semantics if they do | ||
| so. | ||
|
|
||
| The billing anchor for a subscription is the time activation succeeds. |
There was a problem hiding this comment.
services even such as Netflix support resetting the billing cycle anchor - is that a trapdoor decision here?
|
|
||
| | Field | Type | Description | | ||
| |-------|------|-------------| | ||
| | `amount` | string | Fixed payment amount per billing period in base units | |
There was a problem hiding this comment.
most subscription systems do not take a fixed amount, but rather a price and quantity, and compute the amount. Eg, you are buying 5 seats to a SaaS system
|
|
||
| 1. Create or reuse a Stripe Customer for the payer | ||
| 2. Attach or select the challenged `paymentMethod` for that Customer | ||
| 3. Create or reuse a Stripe Price whose amount, currency, and recurring |
There was a problem hiding this comment.
What about the Stripe Product?
There was a problem hiding this comment.
IIRC Stripe requires a Price to belong to a Product, but in this case, product selection does not affect the protocol authorization.
I think we can treat product as an implementation detail here? or we could also specify to be very explicit if desired
There was a problem hiding this comment.
Yes, its an implementation detail in general, but it does create a user-facing entity since the price requires the product to exist
| billing period, and MUST record that cancellation effective time in | ||
| durable local state. The server MAY cancel immediately only if the | ||
| application separately handles any already-paid access period without | ||
| creating an additional charge. |
There was a problem hiding this comment.
Can you un-cancel before period end?
There was a problem hiding this comment.
to keep things simple for now let's say no
| object still matches this profile before treating the retry as | ||
| successful. | ||
|
|
||
| ## Unsupported Stripe Billing Features |
There was a problem hiding this comment.
What did we decide would happen if the user goes and modifies the stripe subscription and adds one of these features?
There was a problem hiding this comment.
at this point we would consider this "out of protocol" and something that I think could be disputed along the given rail (this is easier to do in stripe vs. in crypto which is strictly defined by the protocol)
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
Closes the gap surfaced after the gateway-side MPP subscription work (tempoxyz/mpp-specs#230): agents using the MCP could probe one-shot MPP endpoints via try_mpp_endpoint but had no way to discover, activate, or manage MPP-protocol subscriptions. Three new tools added: - try_mpp_subscription — probe a public /mpp/sub/{tenant_short_id}/ {resource_id} URL, parse the 402 dual envelope (RFC 9457 or canonical x402 v2), surface subscription-specific extras (periodCount, periodUnit, intent) so the agent can present terms before signing. Unauthenticated; works against any MPP subscription URL. - list_mpp_subscriptions — admin tool, calls GET /internal/tenants/{tenant_id}/mpp-subscriptions. Optional status filter and pagination. Requires admin-scope API key. - cancel_mpp_subscription — admin tool, calls POST /internal/tenants/{tenant_id}/mpp-subscriptions/{id}/cancel. Idempotent — already-cancelled subscriptions return safely. Current period's access (paid) is unaffected. Also adds MPP_SUBSCRIPTION_EVENT_TYPES to schemas.py covering the four webhook events queued by the gateway: mpp_subscription.activated/charged/revoked/expired E2E verified against all 7 chain subscription fixtures (Algorand, VOI, Solana, Hedera, Stellar, Tempo testnets + Base sepolia mainnet-style). Each returns the correct accepts[] shape with period=1/day, amount and per-chain payTo populated. Unknown-resource URL correctly returns 404. Schema-strict-mode parse rejects: non-HTTPS URLs, unknown extra fields. Schema sanity-check count bumped 25 -> 28 (`assert len(_EXPECTED) == 28`).
Version bump from 1.4.x to 1.5.0 across Python (algovoi-mcp) and TypeScript (@algovoi/mcp-server) packages. What's new in 1.5.0: 1. MPP subscription tools (3 new) for tempoxyz/mpp-specs#230 surface: - try_mpp_subscription (unauth probe of public /mpp/sub URLs) - list_mpp_subscriptions (admin) - cancel_mpp_subscription (admin) Already added in 374fe8f; this commit only handles the release-wrap. 2. ARC testnet support — new network entry "arc_testnet" in both Python NETWORKS and TypeScript NETWORKS arrays plus NETWORK_INFO metadata. Tool count now 28 across 8 chains (was 25 across 7). 3. VOI USDC description fix: "ARC-200 302190" → "native ASA 302190" (accuracy correction — 302190 is an Algorand-format ASA, not ARC-200 token contract). 4. smoke_mcp_full.py expects 26 list_networks entries (13 mainnet + 12 testnet + 1 arc_testnet). 5. Package descriptions updated to "28 tools across all 8 AlgoVoi chains" in pyproject.toml + typescript/package.json. User-Agent bumped to algovoi-mcp/1.5.0 in client.py + MCP server-info version bumped in typescript/src/index.ts. Schema sanity check at import: assert len(SCHEMAS_BY_TOOL) == 28. Webhook event catalog gains MPP_SUBSCRIPTION_EVENT_TYPES tuple (mpp_subscription.activated/charged/revoked/expired) for the verify_webhook tool's event-type catalog visibility.
|
Merging this in as this has now been implemented in Tempo, with Stripe support to follow. We will open incremental PRs if there are any changes needed |
Closes the gap surfaced after the gateway-side MPP subscription work (tempoxyz/mpp-specs#230): agents using the MCP could probe one-shot MPP endpoints via try_mpp_endpoint but had no way to discover, activate, or manage MPP-protocol subscriptions. Three new tools added: - try_mpp_subscription — probe a public /mpp/sub/{tenant_short_id}/ {resource_id} URL, parse the 402 dual envelope (RFC 9457 or canonical x402 v2), surface subscription-specific extras (periodCount, periodUnit, intent) so the agent can present terms before signing. Unauthenticated; works against any MPP subscription URL. - list_mpp_subscriptions — admin tool, calls GET /internal/tenants/{tenant_id}/mpp-subscriptions. Optional status filter and pagination. Requires admin-scope API key. - cancel_mpp_subscription — admin tool, calls POST /internal/tenants/{tenant_id}/mpp-subscriptions/{id}/cancel. Idempotent — already-cancelled subscriptions return safely. Current period's access (paid) is unaffected. Also adds MPP_SUBSCRIPTION_EVENT_TYPES to schemas.py covering the four webhook events queued by the gateway: mpp_subscription.activated/charged/revoked/expired E2E verified against all 7 chain subscription fixtures (Algorand, VOI, Solana, Hedera, Stellar, Tempo testnets + Base sepolia mainnet-style). Each returns the correct accepts[] shape with period=1/day, amount and per-chain payTo populated. Unknown-resource URL correctly returns 404. Schema-strict-mode parse rejects: non-HTTPS URLs, unknown extra fields. Schema sanity-check count bumped 25 -> 28 (`assert len(_EXPECTED) == 28`).
Version bump from 1.4.x to 1.5.0 across Python (algovoi-mcp) and TypeScript (@algovoi/mcp-server) packages. What's new in 1.5.0: 1. MPP subscription tools (3 new) for tempoxyz/mpp-specs#230 surface: - try_mpp_subscription (unauth probe of public /mpp/sub URLs) - list_mpp_subscriptions (admin) - cancel_mpp_subscription (admin) Already added in 374fe8f; this commit only handles the release-wrap. 2. ARC testnet support — new network entry "arc_testnet" in both Python NETWORKS and TypeScript NETWORKS arrays plus NETWORK_INFO metadata. Tool count now 28 across 8 chains (was 25 across 7). 3. VOI USDC description fix: "ARC-200 302190" → "native ASA 302190" (accuracy correction — 302190 is an Algorand-format ASA, not ARC-200 token contract). 4. smoke_mcp_full.py expects 26 list_networks entries (13 mainnet + 12 testnet + 1 arc_testnet). 5. Package descriptions updated to "28 tools across all 8 AlgoVoi chains" in pyproject.toml + typescript/package.json. User-Agent bumped to algovoi-mcp/1.5.0 in client.py + MCP server-info version bumped in typescript/src/index.ts. Schema sanity check at import: assert len(SCHEMAS_BY_TOOL) == 28. Webhook event catalog gains MPP_SUBSCRIPTION_EVENT_TYPES tuple (mpp_subscription.activated/charged/revoked/expired) for the verify_webhook tool's event-type catalog visibility.
Summary
This PR adds the
subscriptionpayment intent for recurring fixed-amount Payment authentication, plus Stripe and Tempo method profiles.The shared intent defines activation, renewal, cancellation, subscription identifiers, canonical period semantics, durable accounting/idempotency, receipts, and error behavior.
Stripe maps the intent to a constrained Stripe Billing profile. Tempo maps it to TIP-1011 periodic access-key authorizations.
Notable design decisions / tightening of spec
The shared intent is intentionally narrow: fixed amount, one charge per billing period. It does not model full billing-product behavior like plans, seats, prorations, trials, discounts, metered usage, or plan changes.
Payment methods must reject request shapes they cannot represent exactly. Shared periods support fixed elapsed day/week periods and calendar-month periods anchored at activation. Stripe supports exact day/week/month cadence; Tempo supports day/week and rejects month because TIP-1011 periods are fixed elapsed seconds.
Activation includes the first billing-period charge. A subscription is not active until setup and the first charge succeed, and the receipt includes a
subscriptionId.Renewals require durable per-period accounting. Missed periods do not accumulate extra charge authority, and duplicate requests, retries, webhooks, or concurrent requests must not produce duplicate charges.
Stripe is constrained to one customer, one subscription, one recurring price, quantity 1, synchronous first-invoice payment, and validated paid invoices mapped to canonical billing periods.
Tempo requires bounded authorization with
subscriptionExpires, exactTokenLimitamount/period matching, recipient-scoped transfer selectors, source verification, and access-key isolation guidance.Control flow
flowchart TD A["Client requests protected resource"] --> B{"Active subscription for resource?"} B -- "yes, current period paid" --> C["Return receipt + resource"] B -- "yes, new unpaid period" --> D["Collect renewal via method profile"] D --> E["Durably record paid period"] E --> C B -- "no / expired / canceled / revoked" --> F["Return 402 subscription challenge"] F --> G["Client signs method-specific subscription credential"] G --> H["Server verifies challenge, request, payer, method scope"] H --> I{"Payment method"} I -- "Stripe" --> J["Create/reuse constrained Stripe subscription + paid first invoice"] I -- "Tempo" --> K["Register scoped access key + transfer first charge"] J --> L["Record subscription state + billing anchor"] K --> L L --> M["Return Payment-Receipt with subscriptionId"]