@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.
@@ -0,0 +1,77 @@
1
+ import { type Amount, type Payment } from "@antelopejs/interface-payment";
2
+ import { type Subscription, type SubscriptionRequest } from "../index";
3
+ export declare const SUITE_TIMEOUT = 60000;
4
+ export declare const PERIOD_VALUE = 1900;
5
+ export declare const RETURN_URL = "https://merchant.invalid/return/subscription";
6
+ export declare const DAY_MS: number;
7
+ export declare const SHORTEST_MONTH_MS: number;
8
+ export declare const LONGEST_MONTH_MS: number;
9
+ export declare const TRIAL_DAYS = 14;
10
+ /**
11
+ * Providers keep creation times and schedule times to the second. Two
12
+ * subscriptions created within the same second may list in either order, and a
13
+ * requested trial end or billing anchor comes back truncated to the second.
14
+ */
15
+ export declare const PROVIDER_CLOCK_RESOLUTION_MS = 1100;
16
+ /** A time `ahead` from now, on a whole second, which every provider can echo exactly. */
17
+ export declare function inWholeSeconds(ahead: number): number;
18
+ /** An identifier no provider has a subscription for. */
19
+ export declare const ABSENT_ID: string;
20
+ /**
21
+ * A provider-specific token that the payment interface charges successfully
22
+ * on-session with `savePaymentMethod`, and whose saved method it then charges
23
+ * off-session. The same variable the payment interface's suite reads.
24
+ */
25
+ export declare const TEST_METHOD: string | undefined;
26
+ /**
27
+ * A provider-specific token that saves on-session, and whose saved method the
28
+ * provider declines off-session.
29
+ *
30
+ * There is no portable way to obtain one. Without it, the declined-first-period
31
+ * test is skipped.
32
+ */
33
+ export declare const TEST_METHOD_DECLINES_LATER: string | undefined;
34
+ /**
35
+ * Set when the implementation's events can reach this process during a test
36
+ * run. A native provider delivers them from webhooks, which a test container
37
+ * cannot usually receive; the generic fallback raises them in-process.
38
+ */
39
+ export declare const EVENTS_OBSERVABLE: boolean;
40
+ export declare const CHARGEABLE = "requires ANTELOPE_PAYMENT_TEST_METHOD";
41
+ export declare const DECLINES_LATER = "requires ANTELOPE_SUBSCRIPTIONS_TEST_METHOD_DECLINES_LATER";
42
+ export declare const OBSERVABLE = "requires ANTELOPE_SUBSCRIPTIONS_TEST_EVENTS";
43
+ export declare function amount(value: number): Amount;
44
+ export declare function key(): string;
45
+ export declare function reference(): string;
46
+ /**
47
+ * Asserts a timestamp is epoch milliseconds rather than epoch seconds, and is
48
+ * not further from now than `horizon` allows.
49
+ */
50
+ export declare function assertEpochMillis(value: number, label: string, horizon?: number): void;
51
+ /**
52
+ * Asserts the invariants every subscription holds, whatever its state.
53
+ */
54
+ export declare function assertSubscriptionShape(subscription: Subscription, request: SubscriptionRequest): void;
55
+ /**
56
+ * Polls until the payment leaves `pending`, the only status an off-session
57
+ * charge can sit in.
58
+ */
59
+ export declare function settledPayment(id: string): Promise<Payment>;
60
+ /**
61
+ * A customer with a method saved the only way the contract allows: an
62
+ * on-session payment that saves it, taken with the payer present.
63
+ */
64
+ export interface Payer {
65
+ customer: string;
66
+ paymentMethod: string;
67
+ }
68
+ export declare function newPayer(token?: string | undefined): Promise<Payer>;
69
+ /** Saves another method against an existing customer and returns its id. */
70
+ export declare function saveMethod(customer: string, token?: string | undefined): Promise<string>;
71
+ export declare function monthly(payer: Payer, overrides?: Partial<SubscriptionRequest>): SubscriptionRequest;
72
+ export declare function subscribe(payer: Payer, overrides?: Partial<SubscriptionRequest>): Promise<Subscription>;
73
+ /**
74
+ * Asserts that a call rejects with exactly the given error class, rather than
75
+ * any Error at all.
76
+ */
77
+ export declare function rejectsWith(work: Promise<unknown>, expected: abstract new (...args: never[]) => Error, label: string): Promise<Error>;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OBSERVABLE = exports.DECLINES_LATER = exports.CHARGEABLE = exports.EVENTS_OBSERVABLE = exports.TEST_METHOD_DECLINES_LATER = exports.TEST_METHOD = exports.ABSENT_ID = exports.PROVIDER_CLOCK_RESOLUTION_MS = exports.TRIAL_DAYS = exports.LONGEST_MONTH_MS = exports.SHORTEST_MONTH_MS = exports.DAY_MS = exports.RETURN_URL = exports.PERIOD_VALUE = exports.SUITE_TIMEOUT = void 0;
7
+ exports.inWholeSeconds = inWholeSeconds;
8
+ exports.amount = amount;
9
+ exports.key = key;
10
+ exports.reference = reference;
11
+ exports.assertEpochMillis = assertEpochMillis;
12
+ exports.assertSubscriptionShape = assertSubscriptionShape;
13
+ exports.settledPayment = settledPayment;
14
+ exports.newPayer = newPayer;
15
+ exports.saveMethod = saveMethod;
16
+ exports.monthly = monthly;
17
+ exports.subscribe = subscribe;
18
+ exports.rejectsWith = rejectsWith;
19
+ const strict_1 = __importDefault(require("node:assert/strict"));
20
+ const node_crypto_1 = require("node:crypto");
21
+ const promises_1 = require("node:timers/promises");
22
+ const interface_payment_1 = require("@antelopejs/interface-payment");
23
+ const index_1 = require("../index");
24
+ exports.SUITE_TIMEOUT = 60_000;
25
+ const SETTLE_TIMEOUT = 30_000;
26
+ const SETTLE_INTERVAL = 500;
27
+ exports.PERIOD_VALUE = 1900;
28
+ exports.RETURN_URL = "https://merchant.invalid/return/subscription";
29
+ exports.DAY_MS = 24 * 60 * 60 * 1000;
30
+ const SHORTEST_MONTH_DAYS = 28;
31
+ const LONGEST_MONTH_DAYS = 31;
32
+ exports.SHORTEST_MONTH_MS = SHORTEST_MONTH_DAYS * exports.DAY_MS;
33
+ exports.LONGEST_MONTH_MS = LONGEST_MONTH_DAYS * exports.DAY_MS;
34
+ exports.TRIAL_DAYS = 14;
35
+ /**
36
+ * Providers keep creation times and schedule times to the second. Two
37
+ * subscriptions created within the same second may list in either order, and a
38
+ * requested trial end or billing anchor comes back truncated to the second.
39
+ */
40
+ exports.PROVIDER_CLOCK_RESOLUTION_MS = 1100;
41
+ const SECOND_MS = 1000;
42
+ /** A time `ahead` from now, on a whole second, which every provider can echo exactly. */
43
+ function inWholeSeconds(ahead) {
44
+ return Math.floor((Date.now() + ahead) / SECOND_MS) * SECOND_MS;
45
+ }
46
+ /** An identifier no provider has a subscription for. */
47
+ exports.ABSENT_ID = `antelope-absent-${"0".repeat(24)}`;
48
+ /**
49
+ * A provider-specific token that the payment interface charges successfully
50
+ * on-session with `savePaymentMethod`, and whose saved method it then charges
51
+ * off-session. The same variable the payment interface's suite reads.
52
+ */
53
+ exports.TEST_METHOD = process.env.ANTELOPE_PAYMENT_TEST_METHOD;
54
+ /**
55
+ * A provider-specific token that saves on-session, and whose saved method the
56
+ * provider declines off-session.
57
+ *
58
+ * There is no portable way to obtain one. Without it, the declined-first-period
59
+ * test is skipped.
60
+ */
61
+ exports.TEST_METHOD_DECLINES_LATER = process.env.ANTELOPE_SUBSCRIPTIONS_TEST_METHOD_DECLINES_LATER;
62
+ /**
63
+ * Set when the implementation's events can reach this process during a test
64
+ * run. A native provider delivers them from webhooks, which a test container
65
+ * cannot usually receive; the generic fallback raises them in-process.
66
+ */
67
+ exports.EVENTS_OBSERVABLE = Boolean(process.env.ANTELOPE_SUBSCRIPTIONS_TEST_EVENTS);
68
+ const CURRENCY = process.env.ANTELOPE_PAYMENT_TEST_CURRENCY || "EUR";
69
+ exports.CHARGEABLE = "requires ANTELOPE_PAYMENT_TEST_METHOD";
70
+ exports.DECLINES_LATER = "requires ANTELOPE_SUBSCRIPTIONS_TEST_METHOD_DECLINES_LATER";
71
+ exports.OBSERVABLE = "requires ANTELOPE_SUBSCRIPTIONS_TEST_EVENTS";
72
+ function amount(value) {
73
+ return { value, currency: CURRENCY };
74
+ }
75
+ function key() {
76
+ return `antelope-conformance-${(0, node_crypto_1.randomUUID)()}`;
77
+ }
78
+ function reference() {
79
+ return `conformance-${(0, node_crypto_1.randomUUID)()}`;
80
+ }
81
+ const EPOCH_MILLIS_FLOOR = Date.UTC(2020, 0, 1);
82
+ const CLOCK_SKEW_ALLOWANCE = 5 * 60 * 1000;
83
+ /**
84
+ * Asserts a timestamp is epoch milliseconds rather than epoch seconds, and is
85
+ * not further from now than `horizon` allows.
86
+ */
87
+ function assertEpochMillis(value, label, horizon = CLOCK_SKEW_ALLOWANCE) {
88
+ strict_1.default.ok(Number.isInteger(value), `${label} must be an integer`);
89
+ strict_1.default.ok(value > EPOCH_MILLIS_FLOOR, `${label} is ${value}, which is not epoch milliseconds (seconds, perhaps?)`);
90
+ strict_1.default.ok(value < Date.now() + horizon, `${label} is ${value}, further ahead than expected`);
91
+ }
92
+ /**
93
+ * Asserts the invariants every subscription holds, whatever its state.
94
+ */
95
+ function assertSubscriptionShape(subscription, request) {
96
+ strict_1.default.ok(subscription.id.length > 0, "subscription id must be non-empty");
97
+ strict_1.default.ok(index_1.SUBSCRIPTION_STATUSES.includes(subscription.status), `status ${subscription.status} is outside the contract`);
98
+ strict_1.default.equal(subscription.reference, request.reference);
99
+ strict_1.default.equal(subscription.customer, request.customer);
100
+ strict_1.default.equal(subscription.interval, request.interval);
101
+ strict_1.default.equal(subscription.intervalCount, request.intervalCount ?? 1);
102
+ strict_1.default.deepEqual(subscription.amount, request.amount);
103
+ strict_1.default.equal(typeof subscription.cancelAtPeriodEnd, "boolean");
104
+ assertEpochMillis(subscription.createdAt, "createdAt");
105
+ assertEpochMillis(subscription.currentPeriodStart, "currentPeriodStart");
106
+ assertEpochMillis(subscription.currentPeriodEnd, "currentPeriodEnd", 400 * exports.DAY_MS);
107
+ strict_1.default.ok(subscription.currentPeriodEnd > subscription.currentPeriodStart, "a period must end after it starts");
108
+ const isCanceled = subscription.status === "canceled";
109
+ strict_1.default.equal(subscription.endedAt !== undefined, isCanceled, "endedAt is present exactly when the subscription has ended");
110
+ strict_1.default.equal(subscription.endReason !== undefined, isCanceled, "endReason is present exactly when the subscription has ended");
111
+ }
112
+ /**
113
+ * Polls until the payment leaves `pending`, the only status an off-session
114
+ * charge can sit in.
115
+ */
116
+ async function settledPayment(id) {
117
+ const deadline = Date.now() + SETTLE_TIMEOUT;
118
+ let payment = await (0, interface_payment_1.GetPayment)(id);
119
+ while (!(0, interface_payment_1.isTerminalPaymentStatus)(payment.status) && Date.now() < deadline) {
120
+ await (0, promises_1.setTimeout)(SETTLE_INTERVAL);
121
+ payment = await (0, interface_payment_1.GetPayment)(id);
122
+ }
123
+ return payment;
124
+ }
125
+ const customers = [];
126
+ after(async () => {
127
+ await Promise.all(customers.map((id) => (0, interface_payment_1.DeleteCustomer)(id, key()).catch(() => undefined)));
128
+ });
129
+ async function newPayer(token = exports.TEST_METHOD) {
130
+ const customer = await (0, interface_payment_1.CreateCustomer)({ email: `${(0, node_crypto_1.randomUUID)()}@merchant.invalid`, name: "Conformance Payer" }, key());
131
+ customers.push(customer.id);
132
+ return {
133
+ customer: customer.id,
134
+ paymentMethod: await saveMethod(customer.id, token),
135
+ };
136
+ }
137
+ /** Saves another method against an existing customer and returns its id. */
138
+ async function saveMethod(customer, token = exports.TEST_METHOD) {
139
+ const before = await (0, interface_payment_1.ListPaymentMethods)(customer);
140
+ const payment = await (0, interface_payment_1.CreatePayment)({
141
+ amount: amount(exports.PERIOD_VALUE),
142
+ reference: reference(),
143
+ customer,
144
+ paymentMethod: token,
145
+ savePaymentMethod: true,
146
+ returnUrl: exports.RETURN_URL,
147
+ }, key());
148
+ const settled = await settledPayment(payment.id);
149
+ strict_1.default.equal(settled.status, "succeeded", "the saving payment must succeed");
150
+ const known = new Set(before.map((method) => method.id));
151
+ const saved = (await (0, interface_payment_1.ListPaymentMethods)(customer)).find((method) => !known.has(method.id));
152
+ strict_1.default.ok(saved, "the saving payment must leave a method saved");
153
+ return saved.id;
154
+ }
155
+ function monthly(payer, overrides = {}) {
156
+ return {
157
+ reference: reference(),
158
+ customer: payer.customer,
159
+ paymentMethod: payer.paymentMethod,
160
+ amount: amount(exports.PERIOD_VALUE),
161
+ interval: "month",
162
+ description: "Conformance subscription",
163
+ ...overrides,
164
+ };
165
+ }
166
+ async function subscribe(payer, overrides = {}) {
167
+ const request = monthly(payer, overrides);
168
+ const subscription = await (0, index_1.CreateSubscription)(request, key());
169
+ assertSubscriptionShape(subscription, request);
170
+ return subscription;
171
+ }
172
+ /**
173
+ * Asserts that a call rejects with exactly the given error class, rather than
174
+ * any Error at all.
175
+ */
176
+ async function rejectsWith(work, expected, label) {
177
+ const error = await work.then(() => undefined, (reason) => reason);
178
+ strict_1.default.ok(error instanceof expected, `${label}: expected ${expected.name}, got ${String(error)}`);
179
+ return error;
180
+ }
181
+ //# sourceMappingURL=support.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"support.js","sourceRoot":"","sources":["../../src/tests/support.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,228 @@
1
+ import type { Amount } from "@antelopejs/interface-payment";
2
+ /**
3
+ * Every value {@link SubscriptionInterval} can take.
4
+ */
5
+ export declare const SUBSCRIPTION_INTERVALS: readonly ["day", "week", "month", "year"];
6
+ /**
7
+ * The unit a billing period is counted in.
8
+ *
9
+ * Calendar units are calendar units: a `month` period that starts on 31 January
10
+ * ends on the last day of February, and the one after it ends on 31 March. The
11
+ * day of the month comes from the billing anchor, not from the end of the
12
+ * previous period, so short months do not drift the cycle.
13
+ */
14
+ export type SubscriptionInterval = (typeof SUBSCRIPTION_INTERVALS)[number];
15
+ /**
16
+ * The longest billing period every implementation must accept: one year.
17
+ *
18
+ * Providers cap the period differently. A request for a longer one may be
19
+ * refused with {@link InvalidSubscriptionRequestError}.
20
+ */
21
+ export declare const MAX_PORTABLE_PERIOD_DAYS = 366;
22
+ /**
23
+ * Every value {@link SubscriptionStatus} can take, in lifecycle order.
24
+ */
25
+ export declare const SUBSCRIPTION_STATUSES: readonly ["trialing", "active", "past_due", "canceled"];
26
+ /**
27
+ * Normalised subscription status.
28
+ *
29
+ * | Status | Meaning |
30
+ * | --- | --- |
31
+ * | `trialing` | Before {@link Subscription.trialEnd}. Nothing has been charged. |
32
+ * | `active` | The current period is paid for, or not yet due. |
33
+ * | `past_due` | Charging the current period failed. The implementation is retrying under its own policy. |
34
+ * | `canceled` | Ended. Terminal: nothing is charged again. |
35
+ *
36
+ * `past_due` is reported the same way everywhere, but what happens next is
37
+ * not portable: native providers follow their configured retry schedule, and
38
+ * the generic fallback follows its documented policy. Decide access by status;
39
+ * do not predict when a `past_due` subscription will recover or end.
40
+ */
41
+ export type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];
42
+ /**
43
+ * Whether a subscription has ended for good.
44
+ *
45
+ * @param status - The status to classify
46
+ * @returns true for `canceled` only
47
+ */
48
+ export declare function isTerminalSubscriptionStatus(status: SubscriptionStatus): boolean;
49
+ /**
50
+ * Every value {@link SubscriptionEndReason} can take.
51
+ */
52
+ export declare const SUBSCRIPTION_END_REASONS: readonly ["requested", "payment_failed"];
53
+ /**
54
+ * Why a subscription ended: someone cancelled it, or the implementation gave up
55
+ * charging it.
56
+ */
57
+ export type SubscriptionEndReason = (typeof SUBSCRIPTION_END_REASONS)[number];
58
+ /**
59
+ * Every value {@link CancellationTiming} can take.
60
+ */
61
+ export declare const CANCELLATION_TIMINGS: readonly ["now", "period_end"];
62
+ /**
63
+ * When {@link CancelSubscription} takes effect.
64
+ *
65
+ * - `now` ends the subscription immediately. Nothing is refunded: a refund is a
66
+ * separate `RefundPayment` through the payment interface.
67
+ * - `period_end` lets the period that has been paid for run out, and ends the
68
+ * subscription then instead of renewing it. The status does not change until
69
+ * it ends.
70
+ */
71
+ export type CancellationTiming = (typeof CANCELLATION_TIMINGS)[number];
72
+ /**
73
+ * A subscription, in whatever state it was last reported in.
74
+ *
75
+ * `id` is opaque. Correlate with your own records through
76
+ * {@link Subscription.reference}.
77
+ */
78
+ export interface Subscription {
79
+ /** Opaque identifier. */
80
+ id: string;
81
+ /** The caller's own identifier, echoed back from the request. */
82
+ reference: string;
83
+ /** Opaque payment-interface customer identifier. */
84
+ customer: string;
85
+ /** Opaque payment-interface identifier of the saved method charged. */
86
+ paymentMethod: string;
87
+ /** Charged once per period. */
88
+ amount: Amount;
89
+ /** The unit a period is counted in. */
90
+ interval: SubscriptionInterval;
91
+ /** How many `interval`s make one period. At least 1. */
92
+ intervalCount: number;
93
+ /** Normalised status. */
94
+ status: SubscriptionStatus;
95
+ /**
96
+ * Unix epoch milliseconds. Start of the period most recently billed for, or
97
+ * of the trial or pre-anchor period before the first charge.
98
+ */
99
+ currentPeriodStart: number;
100
+ /**
101
+ * Unix epoch milliseconds. When the current period ends and the next one is
102
+ * charged, unless {@link Subscription.cancelAtPeriodEnd} is set.
103
+ */
104
+ currentPeriodEnd: number;
105
+ /** Unix epoch milliseconds. Present when the subscription began with a trial. */
106
+ trialEnd?: number;
107
+ /** The subscription ends at `currentPeriodEnd` instead of renewing. */
108
+ cancelAtPeriodEnd: boolean;
109
+ /** Unix epoch milliseconds. Present exactly when `status` is `canceled`. */
110
+ endedAt?: number;
111
+ /** Present exactly when `status` is `canceled`. */
112
+ endReason?: SubscriptionEndReason;
113
+ /**
114
+ * Opaque payment-interface identifier of the most recent charge attempt.
115
+ * Absent until something has been charged.
116
+ */
117
+ latestPayment?: string;
118
+ /** Unix epoch milliseconds. */
119
+ createdAt: number;
120
+ /** The metadata the subscription was created with. */
121
+ metadata?: Record<string, string>;
122
+ /**
123
+ * What the provider said about this subscription, verbatim, for logs and
124
+ * support tickets.
125
+ *
126
+ * Read-only. Nothing a caller does with this object reaches the provider, and
127
+ * no request type accepts it: provider-specific behaviour belongs in the
128
+ * provider's own interface, where the coupling is visible.
129
+ */
130
+ vendorData?: Readonly<Record<string, unknown>>;
131
+ }
132
+ /**
133
+ * Parameters for {@link CreateSubscription}.
134
+ *
135
+ * The payer is not present. Take the mandate first, with an on-session
136
+ * payment-interface payment that sets `savePaymentMethod`, and pass the method
137
+ * it saved.
138
+ */
139
+ export interface SubscriptionRequest {
140
+ /** Your own identifier for this subscription. */
141
+ reference: string;
142
+ /** Opaque payment-interface customer identifier. */
143
+ customer: string;
144
+ /**
145
+ * Opaque payment-interface identifier of a method saved against `customer`
146
+ * and chargeable off-session, from `ListPaymentMethods`.
147
+ */
148
+ paymentMethod: string;
149
+ /** Charged once per period. A positive integer of minor units. */
150
+ amount: Amount;
151
+ /** The unit a period is counted in. */
152
+ interval: SubscriptionInterval;
153
+ /** How many `interval`s make one period. Defaults to 1. */
154
+ intervalCount?: number;
155
+ /**
156
+ * Unix epoch milliseconds. Start with a free trial that ends here; the first
157
+ * charge is made at this time. Must be in the future. Mutually exclusive with
158
+ * `billingAnchor`. Kept at the provider's precision, which is at least the
159
+ * second: pass a whole second to have it echoed exactly.
160
+ */
161
+ trialEnd?: number;
162
+ /**
163
+ * Unix epoch milliseconds. Start the first paid period here rather than now;
164
+ * nothing is charged before it, and the status is `active` meanwhile. Must
165
+ * be in the future. Mutually exclusive with `trialEnd`. Kept at the
166
+ * provider's precision, which is at least the second.
167
+ */
168
+ billingAnchor?: number;
169
+ /** Shown to the payer where supported. */
170
+ description?: string;
171
+ /** Round-tripped opaque key/value pairs. */
172
+ metadata?: Record<string, string>;
173
+ }
174
+ /**
175
+ * Every value {@link SubscriptionEventType} can take.
176
+ */
177
+ export declare const SUBSCRIPTION_EVENT_TYPES: readonly ["subscription.created", "subscription.payment_succeeded", "subscription.payment_failed", "subscription.canceled"];
178
+ /**
179
+ * Normalised subscription event type.
180
+ *
181
+ * | Type | When |
182
+ * | --- | --- |
183
+ * | `subscription.created` | The subscription exists. |
184
+ * | `subscription.payment_succeeded` | A period was paid for, including the first. |
185
+ * | `subscription.payment_failed` | Charging a period failed; the status is `past_due`. |
186
+ * | `subscription.canceled` | The subscription ended, for any {@link SubscriptionEndReason}. |
187
+ */
188
+ export type SubscriptionEventType = (typeof SUBSCRIPTION_EVENT_TYPES)[number];
189
+ /**
190
+ * Something that happened to a subscription, normalised.
191
+ *
192
+ * Delivered at least once. De-duplicate on {@link SubscriptionEvent.id}.
193
+ */
194
+ export interface SubscriptionEvent {
195
+ /** Opaque identifier, stable across redeliveries of the same event. */
196
+ id: string;
197
+ /** Normalised type. */
198
+ type: SubscriptionEventType;
199
+ /** Unix epoch milliseconds. */
200
+ createdAt: number;
201
+ /** The subscription as it was when the event happened. */
202
+ subscription: Subscription;
203
+ /**
204
+ * Opaque payment-interface identifier of the charge behind a
205
+ * `subscription.payment_*` event. Read it with `GetPayment`.
206
+ *
207
+ * Absent from a `subscription.payment_failed` when the provider refused to
208
+ * make the charge at all — the saved method was removed, say — since there is
209
+ * no payment to point at.
210
+ */
211
+ payment?: string;
212
+ }
213
+ /**
214
+ * Receives subscription events.
215
+ *
216
+ * A rejection, or a throw, means the event was not handled and will be
217
+ * delivered again.
218
+ */
219
+ export type SubscriptionEventHandler = (event: SubscriptionEvent) => void | Promise<void>;
220
+ /**
221
+ * The metadata key every charge made for a subscription carries, set to the
222
+ * subscription's identifier.
223
+ *
224
+ * Those charges are ordinary payment-interface payments, so their
225
+ * `payment.*` events reach your payment webhook route as well. Use this key to
226
+ * tell them from one-off payments.
227
+ */
228
+ export declare const SUBSCRIPTION_METADATA_KEY = "antelopeSubscription";
package/dist/types.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUBSCRIPTION_METADATA_KEY = exports.SUBSCRIPTION_EVENT_TYPES = exports.CANCELLATION_TIMINGS = exports.SUBSCRIPTION_END_REASONS = exports.SUBSCRIPTION_STATUSES = exports.MAX_PORTABLE_PERIOD_DAYS = exports.SUBSCRIPTION_INTERVALS = void 0;
4
+ exports.isTerminalSubscriptionStatus = isTerminalSubscriptionStatus;
5
+ /**
6
+ * Every value {@link SubscriptionInterval} can take.
7
+ */
8
+ exports.SUBSCRIPTION_INTERVALS = Object.freeze([
9
+ "day",
10
+ "week",
11
+ "month",
12
+ "year",
13
+ ]);
14
+ /**
15
+ * The longest billing period every implementation must accept: one year.
16
+ *
17
+ * Providers cap the period differently. A request for a longer one may be
18
+ * refused with {@link InvalidSubscriptionRequestError}.
19
+ */
20
+ exports.MAX_PORTABLE_PERIOD_DAYS = 366;
21
+ /**
22
+ * Every value {@link SubscriptionStatus} can take, in lifecycle order.
23
+ */
24
+ exports.SUBSCRIPTION_STATUSES = Object.freeze([
25
+ "trialing",
26
+ "active",
27
+ "past_due",
28
+ "canceled",
29
+ ]);
30
+ /**
31
+ * Whether a subscription has ended for good.
32
+ *
33
+ * @param status - The status to classify
34
+ * @returns true for `canceled` only
35
+ */
36
+ function isTerminalSubscriptionStatus(status) {
37
+ return status === "canceled";
38
+ }
39
+ /**
40
+ * Every value {@link SubscriptionEndReason} can take.
41
+ */
42
+ exports.SUBSCRIPTION_END_REASONS = Object.freeze([
43
+ "requested",
44
+ "payment_failed",
45
+ ]);
46
+ /**
47
+ * Every value {@link CancellationTiming} can take.
48
+ */
49
+ exports.CANCELLATION_TIMINGS = Object.freeze([
50
+ "now",
51
+ "period_end",
52
+ ]);
53
+ /**
54
+ * Every value {@link SubscriptionEventType} can take.
55
+ */
56
+ exports.SUBSCRIPTION_EVENT_TYPES = Object.freeze([
57
+ "subscription.created",
58
+ "subscription.payment_succeeded",
59
+ "subscription.payment_failed",
60
+ "subscription.canceled",
61
+ ]);
62
+ /**
63
+ * The metadata key every charge made for a subscription carries, set to the
64
+ * subscription's identifier.
65
+ *
66
+ * Those charges are ordinary payment-interface payments, so their
67
+ * `payment.*` events reach your payment webhook route as well. Use this key to
68
+ * tell them from one-off payments.
69
+ */
70
+ exports.SUBSCRIPTION_METADATA_KEY = "antelopeSubscription";
71
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA+DA,oEAIC;AAjED;;GAEG;AACU,QAAA,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAClD,KAAK;IACL,MAAM;IACN,OAAO;IACP,MAAM;CACE,CAAC,CAAC;AAYZ;;;;;GAKG;AACU,QAAA,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;GAEG;AACU,QAAA,qBAAqB,GAAG,MAAM,CAAC,MAAM,CAAC;IACjD,UAAU;IACV,QAAQ;IACR,UAAU;IACV,UAAU;CACF,CAAC,CAAC;AAmBZ;;;;;GAKG;AACH,SAAgB,4BAA4B,CAC1C,MAA0B;IAE1B,OAAO,MAAM,KAAK,UAAU,CAAC;AAC/B,CAAC;AAED;;GAEG;AACU,QAAA,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC;IACpD,WAAW;IACX,gBAAgB;CACR,CAAC,CAAC;AAQZ;;GAEG;AACU,QAAA,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,KAAK;IACL,YAAY;CACJ,CAAC,CAAC;AAqHZ;;GAEG;AACU,QAAA,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC;IACpD,sBAAsB;IACtB,gCAAgC;IAChC,6BAA6B;IAC7B,uBAAuB;CACf,CAAC,CAAC;AAiDZ;;;;;;;GAOG;AACU,QAAA,yBAAyB,GAAG,sBAAsB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type SubscriptionRequest } from "./types";
2
+ /**
3
+ * Checks a {@link SubscriptionRequest} against the portable contract, so every
4
+ * implementation refuses the same request shapes. Consumers can supply `now`
5
+ * to also check that trial and billing dates are in the future.
6
+ *
7
+ * Implementations may omit `now` only when their provider validates future
8
+ * dates after checking for an idempotent replay. This lets a saved result be
9
+ * returned after its original date has passed without accepting a new request
10
+ * with a past date. Finite timestamps and all other rules are always checked.
11
+ *
12
+ * @param request - The request to check
13
+ * @param now - Optional Unix epoch milliseconds for relative date validation
14
+ * @throws InvalidSubscriptionRequestError naming the first rule broken
15
+ */
16
+ export declare function ValidateSubscriptionRequest(request: SubscriptionRequest, now?: number): void;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidateSubscriptionRequest = ValidateSubscriptionRequest;
4
+ const errors_1 = require("./errors");
5
+ const types_1 = require("./types");
6
+ function positiveInteger(value) {
7
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
8
+ }
9
+ function validTime(value, now) {
10
+ return (value === undefined ||
11
+ (Number.isFinite(value) && (now === undefined || value > now)));
12
+ }
13
+ const RULES = [
14
+ (request) => request.reference ? undefined : "reference must be a non-empty string",
15
+ (request) => (request.customer ? undefined : "customer is required"),
16
+ (request) => request.paymentMethod ? undefined : "paymentMethod is required",
17
+ (request) => positiveInteger(request.amount?.value)
18
+ ? undefined
19
+ : "amount must be a positive integer of minor units",
20
+ (request) => /^[A-Z]{3}$/.test(request.amount?.currency ?? "")
21
+ ? undefined
22
+ : "amount.currency must be an uppercase ISO-4217 code",
23
+ (request) => types_1.SUBSCRIPTION_INTERVALS.includes(request.interval)
24
+ ? undefined
25
+ : `interval must be one of ${types_1.SUBSCRIPTION_INTERVALS.join(", ")}`,
26
+ (request) => request.intervalCount === undefined ||
27
+ positiveInteger(request.intervalCount)
28
+ ? undefined
29
+ : "intervalCount must be a positive integer",
30
+ (request) => request.trialEnd !== undefined && request.billingAnchor !== undefined
31
+ ? "trialEnd and billingAnchor are mutually exclusive"
32
+ : undefined,
33
+ (request, now) => validTime(request.trialEnd, now)
34
+ ? undefined
35
+ : "trialEnd must be a finite timestamp in the future",
36
+ (request, now) => validTime(request.billingAnchor, now)
37
+ ? undefined
38
+ : "billingAnchor must be a finite timestamp in the future",
39
+ ];
40
+ /**
41
+ * Checks a {@link SubscriptionRequest} against the portable contract, so every
42
+ * implementation refuses the same request shapes. Consumers can supply `now`
43
+ * to also check that trial and billing dates are in the future.
44
+ *
45
+ * Implementations may omit `now` only when their provider validates future
46
+ * dates after checking for an idempotent replay. This lets a saved result be
47
+ * returned after its original date has passed without accepting a new request
48
+ * with a past date. Finite timestamps and all other rules are always checked.
49
+ *
50
+ * @param request - The request to check
51
+ * @param now - Optional Unix epoch milliseconds for relative date validation
52
+ * @throws InvalidSubscriptionRequestError naming the first rule broken
53
+ */
54
+ function ValidateSubscriptionRequest(request, now) {
55
+ const problem = RULES.map((rule) => rule(request, now)).find(Boolean);
56
+ if (problem)
57
+ throw new errors_1.InvalidSubscriptionRequestError(problem);
58
+ }
59
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":";;AAmEA,kEAMC;AAzED,qCAA2D;AAC3D,mCAA2E;AAI3E,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,SAAS,CAAC,KAAyB,EAAE,GAAY;IACxD,OAAO,CACL,KAAK,KAAK,SAAS;QACnB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC,CAC/D,CAAC;AACJ,CAAC;AAED,MAAM,KAAK,GAAW;IACpB,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,sCAAsC;IACxE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC;IACpE,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,2BAA2B;IACjE,CAAC,OAAO,EAAE,EAAE,CACV,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;QACpC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,kDAAkD;IACxD,CAAC,OAAO,EAAE,EAAE,CACV,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC;QAC/C,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,oDAAoD;IAC1D,CAAC,OAAO,EAAE,EAAE,CACV,8BAAsB,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC/C,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,2BAA2B,8BAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;IACpE,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,aAAa,KAAK,SAAS;QACnC,eAAe,CAAC,OAAO,CAAC,aAAa,CAAC;QACpC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,0CAA0C;IAChD,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS;QACnE,CAAC,CAAC,mDAAmD;QACrD,CAAC,CAAC,SAAS;IACf,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CACf,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;QAC9B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,mDAAmD;IACzD,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CACf,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;QACnC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wDAAwD;CAC/D,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,2BAA2B,CACzC,OAA4B,EAC5B,GAAY;IAEZ,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtE,IAAI,OAAO;QAAE,MAAM,IAAI,wCAA+B,CAAC,OAAO,CAAC,CAAC;AAClE,CAAC"}