Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;

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
Expand All @@ -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

Expand Down
60 changes: 42 additions & 18 deletions docs/2.subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,20 +111,35 @@ 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.

## 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
Expand Down
22 changes: 18 additions & 4 deletions docs/3.events.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>;
declare function applyToAccount(event: SubscriptionEvent): Promise<void>;

SubscriptionEvents.register("billing", async (event) => {
if (await alreadyHandled(event.id)) return;
Expand Down Expand Up @@ -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
}
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/4.implementing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
2 changes: 2 additions & 0 deletions docs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"noEmit": true,
"composite": false,
"incremental": false,
Expand Down
Loading