@antelopejs/interface-subscriptions 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +215 -2
- package/dist/antelope.test.d.ts +2 -0
- package/dist/antelope.test.js +10 -0
- package/dist/antelope.test.js.map +1 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.js +85 -0
- package/dist/errors.js.map +1 -0
- package/dist/functions.d.ts +101 -0
- package/dist/functions.js +104 -0
- package/dist/functions.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/subscriptions-changes.test.d.ts +1 -0
- package/dist/tests/subscriptions-changes.test.js +194 -0
- package/dist/tests/subscriptions-changes.test.js.map +1 -0
- package/dist/tests/subscriptions.test.d.ts +1 -0
- package/dist/tests/subscriptions.test.js +144 -0
- package/dist/tests/subscriptions.test.js.map +1 -0
- package/dist/tests/support.d.ts +77 -0
- package/dist/tests/support.js +181 -0
- package/dist/tests/support.js.map +1 -0
- package/dist/types.d.ts +228 -0
- package/dist/types.js +71 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +16 -0
- package/dist/validation.js +59 -0
- package/dist/validation.js.map +1 -0
- package/docs/1.introduction.md +98 -0
- package/docs/2.subscriptions.md +147 -0
- package/docs/3.events.md +79 -0
- package/docs/4.implementing.md +158 -0
- package/docs/5.provider-capability-matrix.md +61 -0
- package/docs/tsconfig.json +14 -0
- package/package.json +80 -4
- package/skills/managing-subscriptions/SKILL.md +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: managing-subscriptions
|
|
3
|
+
description: Creates and cancels AntelopeJS recurring billing and synchronizes access from lifecycle events. Use when adding subscriptions, recovering past_due accounts, or implementing a subscription provider.
|
|
4
|
+
category: antelopejs-interface
|
|
5
|
+
tags: [subscriptions, billing, recurring, payment, antelopejs]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# subscriptions interface
|
|
9
|
+
|
|
10
|
+
## Task workflow
|
|
11
|
+
|
|
12
|
+
1. Read `README.md` for task paths and compatibility. Recommend Stripe native
|
|
13
|
+
billing with Stripe; select `@antelopejs/payment-subscriptions` only as an
|
|
14
|
+
explicit alternative. Assign one renewal owner per subscription.
|
|
15
|
+
2. Follow `docs/2.subscriptions.md`: save a method on-session, confirm success
|
|
16
|
+
or authorization, then subscribe using IDs from the same provider account.
|
|
17
|
+
3. Persist operation keys and subscription/payment IDs. Replay protection lasts
|
|
18
|
+
only for the provider-defined replay window. After expiration, reconcile
|
|
19
|
+
uncertain payments before retrying; the same key can charge again.
|
|
20
|
+
4. Follow `docs/3.events.md` to deduplicate lifecycle events and synchronize
|
|
21
|
+
access from status, not an assumed retry date.
|
|
22
|
+
5. For provider work, read `docs/4.implementing.md`, preserve replay-before-date
|
|
23
|
+
validation, run `pnpm test`, then run conformance with real provider fixtures.
|
|
24
|
+
Completion means creation, cancellation and recovery are checked and skipped
|
|
25
|
+
renewal/webhook coverage is reported explicitly.
|
|
26
|
+
|
|
27
|
+
Recurring billing without a billing cycle in your own module. Every call is an
|
|
28
|
+
`InterfaceFunction`, so it is async and queues until an implementation attaches.
|
|
29
|
+
This package declares the contract and contains no billing logic.
|
|
30
|
+
|
|
31
|
+
Implementations: a provider that fires renewals itself implements it natively; for
|
|
32
|
+
any other payment-interface provider, wire `@antelopejs/payment-subscriptions`
|
|
33
|
+
(it needs `@antelopejs/interface-database`, and uses the Redis scheduler when one
|
|
34
|
+
is wired).
|
|
35
|
+
|
|
36
|
+
## Imports
|
|
37
|
+
|
|
38
|
+
Add `@antelopejs/interface-subscriptions` and `@antelopejs/interface-payment` to
|
|
39
|
+
the module's `dependencies`:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
CreateSubscription,
|
|
44
|
+
GetSubscription,
|
|
45
|
+
ListSubscriptions,
|
|
46
|
+
CancelSubscription,
|
|
47
|
+
UpdateSubscriptionPaymentMethod,
|
|
48
|
+
SubscriptionEvents,
|
|
49
|
+
isTerminalSubscriptionStatus,
|
|
50
|
+
SubscriptionError,
|
|
51
|
+
SubscriptionNotFoundError,
|
|
52
|
+
InvalidSubscriptionRequestError,
|
|
53
|
+
InvalidSubscriptionStateError,
|
|
54
|
+
SubscriptionPaymentError,
|
|
55
|
+
SUBSCRIPTION_STATUSES,
|
|
56
|
+
SUBSCRIPTION_EVENT_TYPES,
|
|
57
|
+
SUBSCRIPTION_METADATA_KEY,
|
|
58
|
+
} from "@antelopejs/interface-subscriptions";
|
|
59
|
+
import type {
|
|
60
|
+
Subscription,
|
|
61
|
+
SubscriptionRequest,
|
|
62
|
+
SubscriptionStatus,
|
|
63
|
+
SubscriptionEvent,
|
|
64
|
+
SubscriptionEventHandler,
|
|
65
|
+
CancellationTiming,
|
|
66
|
+
} from "@antelopejs/interface-subscriptions";
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Five rules that are easy to get wrong
|
|
70
|
+
|
|
71
|
+
1. **Take the mandate first, on-session.** `CreatePayment` with
|
|
72
|
+
`savePaymentMethod: true` and the payer present, then `ListPaymentMethods`.
|
|
73
|
+
`savePaymentMethod` and `offSession` cannot be combined.
|
|
74
|
+
2. **A declined first period throws; a declined renewal does not.** Creation
|
|
75
|
+
rejects with `SubscriptionPaymentError` (and creates nothing). A later failure
|
|
76
|
+
is `status: "past_due"` plus a `subscription.payment_failed` event.
|
|
77
|
+
3. **Decide access from `status`, never from a predicted retry date.** The retry
|
|
78
|
+
policy after `past_due` differs per implementation.
|
|
79
|
+
4. **Events are at least once.** De-duplicate on `event.id`. Register with an id
|
|
80
|
+
unique within your module; handlers unload with it.
|
|
81
|
+
5. **Subscription charges also arrive as `payment.*` webhooks.** Skip any whose
|
|
82
|
+
`payment.metadata[SUBSCRIPTION_METADATA_KEY]` is set; the subscription event is
|
|
83
|
+
the one that says what the charge meant.
|
|
84
|
+
|
|
85
|
+
## Subscribing
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const signupPayment = await CreatePayment(
|
|
89
|
+
{
|
|
90
|
+
amount,
|
|
91
|
+
reference,
|
|
92
|
+
customer,
|
|
93
|
+
paymentMethod: token,
|
|
94
|
+
savePaymentMethod: true,
|
|
95
|
+
returnUrl,
|
|
96
|
+
},
|
|
97
|
+
`save-${accountId}`,
|
|
98
|
+
);
|
|
99
|
+
if (!["succeeded", "authorized"].includes(signupPayment.status)) {
|
|
100
|
+
// Persist the payment ID and resume after verified settlement or payer action.
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const [method] = await ListPaymentMethods(customer);
|
|
104
|
+
if (!method) throw new Error("No saved payment method after signup");
|
|
105
|
+
|
|
106
|
+
const subscription = await CreateSubscription(
|
|
107
|
+
{
|
|
108
|
+
reference: accountId,
|
|
109
|
+
customer,
|
|
110
|
+
paymentMethod: method.id,
|
|
111
|
+
amount: { value: 1900, currency: "EUR" },
|
|
112
|
+
interval: "month", // "day" | "week" | "month" | "year"
|
|
113
|
+
intervalCount: 1,
|
|
114
|
+
trialEnd: Date.now() + 14 * 24 * 60 * 60 * 1000, // optional; or billingAnchor, not both
|
|
115
|
+
},
|
|
116
|
+
`subscribe-${accountId}`, // idempotency key: derive it from your record
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Periods count from the anchor in UTC calendar units: anchored on 31 January, it
|
|
121
|
+
renews on 28 February, then 31 March.
|
|
122
|
+
|
|
123
|
+
## Reacting
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
SubscriptionEvents.register("billing", async (event) => {
|
|
127
|
+
// "subscription.created" | "subscription.payment_succeeded"
|
|
128
|
+
// | "subscription.payment_failed" | "subscription.canceled"
|
|
129
|
+
await syncAccess(event.subscription.reference, event.subscription.status);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Recovering and ending
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await UpdateSubscriptionPaymentMethod(id, newMethodId, key); // next attempt uses it; charges nothing now
|
|
137
|
+
await CancelSubscription(id, key, "period_end"); // runs out the paid period
|
|
138
|
+
await CancelSubscription(id, key, "now"); // ends now; refunds are RefundPayment's job
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Changing a subscription that has ended raises `InvalidSubscriptionStateError`.
|
|
142
|
+
|
|
143
|
+
## Not here, by design
|
|
144
|
+
|
|
145
|
+
Proration, dunning schedules, tax, coupons, metered billing, several items per
|
|
146
|
+
subscription, and writing provider settings through `vendorData` (it is a
|
|
147
|
+
read-only echo). For those, use the provider's own interface alongside this one.
|
|
148
|
+
|
|
149
|
+
## Implementing it
|
|
150
|
+
|
|
151
|
+
`ImplementInterface(await import("@antelopejs/interface-subscriptions"), impl)`
|
|
152
|
+
with the five calls plus `SubscriptionEvents: { register, unregister }`, and list
|
|
153
|
+
the package in `antelopeJs.implements`. The conformance suite in `dist/tests`
|
|
154
|
+
then runs under `ajs module test`; `docs/4.implementing.md` lists its variables.
|