diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90ab75f..9ee1268 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ permissions: jobs: ci: - uses: AntelopeJS/.github/.github/workflows/ci-node.yml@v1 + uses: AntelopeJS/.github/.github/workflows/ci-node.yml@v1-campaign with: docs-files: | README.md diff --git a/README.md b/README.md index 5521ab4..84dd034 100644 --- a/README.md +++ b/README.md @@ -139,14 +139,19 @@ off-session before `CreateSubscription` resolves. Adapt those amounts to your billing policy. Register an idempotent handler for subsequent lifecycle events: ```ts -import { SubscriptionEvents } from "@antelopejs/interface-subscriptions"; +import { + SubscriptionEvents, + type Subscription, +} from "@antelopejs/interface-subscriptions"; + +declare function grantOrRevokeAccess(subscription: Subscription): Promise; SubscriptionEvents.register("billing", async (event) => { await grantOrRevokeAccess(event.subscription); }); ``` -`grantOrRevokeAccess` stands for your application's access policy. Deduplicate +`grantOrRevokeAccess` stands for your application's access policy, and its `declare` line only gives it a type. Deduplicate events by `event.id` as described in [Events](./docs/3.events.md). ## Compatibility @@ -162,7 +167,7 @@ events by `event.id` as described in [Events](./docs/3.events.md). These are the peer requirements in `package.json`. Development uses the same payment-interface semver range. Check your implementing module's requirements -too. `@antelopejs/core` 1.16.0 is a development test runner, not a runtime peer. +too. `@antelopejs/core` is a development test runner, not a runtime peer. ## Conventions diff --git a/docs/2.subscriptions.md b/docs/2.subscriptions.md index 723beb8..7eb0154 100644 --- a/docs/2.subscriptions.md +++ b/docs/2.subscriptions.md @@ -20,17 +20,26 @@ taken from a payer who is there. That first payment is an ordinary one-off; the subscription starts after it, and its first period is a separate charge. ```ts -const subscription = await CreateSubscription( - { - reference: accountId, - customer, - paymentMethod: method.id, - amount: { value: 1900, currency: "EUR" }, - interval: "month", - intervalCount: 1, - }, - `subscribe-${accountId}`, -); +import type { PaymentMethod } from "@antelopejs/interface-payment"; +import { CreateSubscription } from "@antelopejs/interface-subscriptions"; + +export async function subscribe( + accountId: string, + customer: string, + method: PaymentMethod, +) { + return await CreateSubscription( + { + reference: accountId, + customer, + paymentMethod: method.id, + amount: { value: 1900, currency: "EUR" }, + interval: "month", + intervalCount: 1, + }, + `subscribe-${accountId}`, + ); +} ``` Without a trial or a billing anchor, the first period is charged off-session @@ -102,11 +111,22 @@ If the saved method will not work again — an expired card — take a new manda on-session and switch to it: ```ts -await UpdateSubscriptionPaymentMethod( - subscription.id, - newMethodId, - idempotencyKey, -); +import { + UpdateSubscriptionPaymentMethod, + type Subscription, +} from "@antelopejs/interface-subscriptions"; + +export async function switchMethod( + subscription: Subscription, + newMethodId: string, + idempotencyKey: string, +) { + await UpdateSubscriptionPaymentMethod( + subscription.id, + newMethodId, + idempotencyKey, + ); +} ``` Nothing is charged by the switch. The next scheduled attempt uses the new method. @@ -114,8 +134,12 @@ Nothing is charged by the switch. The next scheduled attempt uses the new method ## Cancelling ```ts -await CancelSubscription(id, `cancel-period-end-${id}`, "period_end"); // run out the paid period -await CancelSubscription(id, `cancel-now-${id}`, "now"); // end immediately +import { CancelSubscription } from "@antelopejs/interface-subscriptions"; + +export async function cancel(id: string) { + await CancelSubscription(id, `cancel-period-end-${id}`, "period_end"); // run out the paid period + await CancelSubscription(id, `cancel-now-${id}`, "now"); // end immediately +} ``` Use distinct keys for distinct operations. Reuse a key only when retrying the diff --git a/docs/3.events.md b/docs/3.events.md index f046c4c..95d4a6c 100644 --- a/docs/3.events.md +++ b/docs/3.events.md @@ -24,7 +24,13 @@ event: the caller made those changes and already knows. ## Receiving them ```ts -import { SubscriptionEvents } from "@antelopejs/interface-subscriptions"; +import { + SubscriptionEvents, + type SubscriptionEvent, +} from "@antelopejs/interface-subscriptions"; + +declare function alreadyHandled(eventId: string): Promise; +declare function applyToAccount(event: SubscriptionEvent): Promise; SubscriptionEvents.register("billing", async (event) => { if (await alreadyHandled(event.id)) return; @@ -68,9 +74,17 @@ Every subscription charge is an ordinary payment-interface payment, so its the subscription's identifier: ```ts -const event = await VerifyWebhook(request); -if (event?.payment?.metadata?.[SUBSCRIPTION_METADATA_KEY]) { - return; // a subscription charge: handled by the subscription event +import { + VerifyWebhook, + type WebhookRequest, +} from "@antelopejs/interface-payment"; +import { SUBSCRIPTION_METADATA_KEY } from "@antelopejs/interface-subscriptions"; + +export async function handlePaymentWebhook(request: WebhookRequest) { + const event = await VerifyWebhook(request); + if (event?.payment?.metadata?.[SUBSCRIPTION_METADATA_KEY]) { + return; // a subscription charge: handled by the subscription event + } } ``` diff --git a/docs/4.implementing.md b/docs/4.implementing.md index afa2d44..bfb45a7 100644 --- a/docs/4.implementing.md +++ b/docs/4.implementing.md @@ -61,7 +61,7 @@ own: that would put a mandatory database and scheduler into a payment module. The calls are declared directly as the public names, so the whole module is the declaration: -```ts +```ts fragment import * as subscriptions from "@antelopejs/interface-subscriptions"; import { ImplementInterface } from "@antelopejs/interface-core"; diff --git a/docs/tsconfig.json b/docs/tsconfig.json index c3e8775..fff0882 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,6 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", "noEmit": true, "composite": false, "incremental": false,