@stewardhq/sdk 0.2.0 → 0.3.0
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/README.md +8 -3
- package/dist/_chunks/errors.js +1 -1
- package/dist/_chunks/events.d.ts +15 -1
- package/dist/_chunks/events.js +171 -3
- package/dist/_chunks/index.d.ts +321 -4
- package/dist/_chunks/src.js +41 -5
- package/dist/_chunks/steward.d.ts +3 -3
- package/dist/_chunks/steward.js +8 -4
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/testing.d.ts +33 -3
- package/dist/testing.js +51 -5
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -338,7 +338,7 @@ const fake = createFakeSteward({
|
|
|
338
338
|
});
|
|
339
339
|
fake.connect((req) => webhooks.handle(req)); // alıcı sonradan (ya da webhook.handler); uygulama fake'ten sonra kurulabilir
|
|
340
340
|
const steward = fake.steward; // ya da ürünün kendi createSteward({ fetch: fake.fetch, baseUrl: fake.baseUrl, … })
|
|
341
|
-
await steward.accounts.upsert("org_1", { displayName: "Org" });
|
|
341
|
+
await steward.accounts.upsert("org_1", { displayName: "Org", notificationEmails: ["owner@acme.com"] }); // billing email recipients (optional)
|
|
342
342
|
const session = await steward.checkoutSessions.create("org_1", input);
|
|
343
343
|
await fake.simulate.checkoutCompleted(session.id); // ya da checkoutFailed; hosted oturumda checkoutCanceled da
|
|
344
344
|
|
|
@@ -366,11 +366,16 @@ fake.simulate.outage(true, { mode: "503" }); // ya da "network": fetch
|
|
|
366
366
|
await fake.simulate.deliver("account.state_changed", { ref: "org_1", duplicate: true, outOfOrder: true });
|
|
367
367
|
fake.simulate.redeliver(eventId); // operatörün pod CLI `redeliver-event`'i: aynı event sonraki flush'ta yeniden
|
|
368
368
|
|
|
369
|
+
fake.emails("org_1"); // billing emails steward would send: [{ template, to, locale, eventId, … }]
|
|
370
|
+
|
|
369
371
|
const { request } = signedEvent("subscription.renewed", { accountRef: "org_1" }, { secret });
|
|
370
372
|
await webhooks.handle(request());
|
|
371
373
|
```
|
|
372
374
|
|
|
373
|
-
`fake.requests` gelen istekleri, `fake.events(ref?)` yayınlanan event'leri tutar.
|
|
375
|
+
`fake.requests` gelen istekleri, `fake.events(ref?)` yayınlanan event'leri tutar. `fake.emails(ref?)`
|
|
376
|
+
steward'ın göndereceği faturalandırma e-postalarını servisle aynı kurallarla (şablon, gönderim anındaki
|
|
377
|
+
alaka, alıcılar, dil, kapatılmış şablon) listeler; içerik render edilmez ve gönderim gecikmesizdir
|
|
378
|
+
([e-postalar](../docs/integrate/emails.md#test)). Endpoint `webhook`
|
|
374
379
|
seçeneğiyle oluşturmada açılır; filtre verilmezse yönetim ucunun yeni ürüne verdiği
|
|
375
380
|
`["account.state_changed"]`, `eventTypes: "legacy"` pod CLI'nın filtresiz (15 eski tip) endpoint'i.
|
|
376
381
|
Alıcı bağlanmadan `flush`/`deliver` fırlatır (event'ler outbox'ta bekler); `connect` alıcıyı değiştirir.
|
|
@@ -423,4 +428,4 @@ GitHub Packages `.npmrc` satırı ve Docker `--secret` bağlaması artık gerekm
|
|
|
423
428
|
|
|
424
429
|
Semver. Alan kaldırma/yeniden adlandırma kırıcıdır (major; 1.0 öncesi minor).
|
|
425
430
|
Yayın: `sdk/package.json` sürümü artırılır, `sdk-v<sürüm>` etiketi push edilir
|
|
426
|
-
(`.github/workflows/sdk-publish.yml
|
|
431
|
+
(`.github/workflows/sdk-publish.yml`).
|
package/dist/_chunks/errors.js
CHANGED
package/dist/_chunks/events.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
//#region ../contract/src/accounts.d.ts
|
|
3
|
+
declare const AccountLocaleSchema: z.ZodEnum<{
|
|
4
|
+
tr: "tr";
|
|
5
|
+
en: "en";
|
|
6
|
+
}>;
|
|
3
7
|
declare const AccountUpsertInputSchema: z.ZodObject<{
|
|
4
8
|
displayName: z.ZodString;
|
|
5
9
|
metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
10
|
+
notificationEmails: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEmail>, z.ZodTransform<string[], string[]>>>;
|
|
11
|
+
locale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
12
|
+
tr: "tr";
|
|
13
|
+
en: "en";
|
|
14
|
+
}>>>;
|
|
6
15
|
}, z.core.$strip>;
|
|
7
16
|
type AccountUpsertInput = z.input<typeof AccountUpsertInputSchema>;
|
|
8
17
|
declare const BillingProfileKindSchema: z.ZodEnum<{
|
|
@@ -263,6 +272,11 @@ declare const AccountSchema: z.ZodObject<{
|
|
|
263
272
|
cancelAt: z.ZodNullable<z.ZodISODateTime>;
|
|
264
273
|
}, z.core.$strip>>;
|
|
265
274
|
}, z.core.$strip>;
|
|
275
|
+
notificationEmails: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
276
|
+
locale: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
|
|
277
|
+
tr: "tr";
|
|
278
|
+
en: "en";
|
|
279
|
+
}>>>;
|
|
266
280
|
}, z.core.$strip>;
|
|
267
281
|
type Account = z.infer<typeof AccountSchema>;
|
|
268
282
|
declare const AccountDeleteResponseSchema: z.ZodObject<{
|
|
@@ -534,4 +548,4 @@ declare const PingSchema: z.ZodObject<{
|
|
|
534
548
|
}, z.core.$strip>;
|
|
535
549
|
type Ping = z.infer<typeof PingSchema>;
|
|
536
550
|
//#endregion
|
|
537
|
-
export {
|
|
551
|
+
export { AccountOpenCheckoutSchema as A, AccountUpsertInput as B, AccountDeleteResponse as C, AccountInclude as D, AccountDunningStateSchema as E, AccountSchema as F, BillingProfileSchema as G, BillingProfile as H, AccountState as I, SubscriptionStatus as J, EntitlementSnapshot as K, AccountStateSchema as L, AccountProfileStateSchema as M, AccountScheduledChange as N, AccountLocaleSchema as O, AccountScheduledChangeSchema as P, AccountSubscriptionState as R, Account as S, AccountDunningState as T, BillingProfileInput as U, AccountUpsertInputSchema as V, BillingProfileKindSchema as W, SubscriptionSummary as X, SubscriptionStatusSchema as Y, SubscriptionSummarySchema as Z, PingSchema as _, BillingEvent as a, AccessState as b, EVENT_DETAIL_SCHEMAS as c, EventIdSchema as d, EventType as f, Ping as g, PING_EVENT_TYPE as h, ApiVersionSchema as i, AccountProfileState as j, AccountOpenCheckout as k, EVENT_TYPES as l, LegacyEventType as m, AnyBillingEvent as n, BillingEventOf as o, LEGACY_EVENT_TYPES as p, EntitlementSnapshotSchema as q, AnyBillingEventSchema as r, BillingEventSchema as s, AccountStateChangeCause as t, EventDetail as u, isEventType as v, AccountDeleteResponseSchema as w, AccessStateSchema as x, ACCOUNT_INCLUDES as y, AccountSubscriptionStateSchema as z };
|
package/dist/_chunks/events.js
CHANGED
|
@@ -46,10 +46,174 @@ const ErrorResponseSchema = z.object({
|
|
|
46
46
|
requestId: z.string().optional().catch(void 0)
|
|
47
47
|
});
|
|
48
48
|
//#endregion
|
|
49
|
+
//#region ../contract/src/emails.ts
|
|
50
|
+
const EMAIL_TEMPLATES = [
|
|
51
|
+
"subscription_started",
|
|
52
|
+
"payment_receipt",
|
|
53
|
+
"payment_failed",
|
|
54
|
+
"cancel_scheduled",
|
|
55
|
+
"subscription_ended",
|
|
56
|
+
"access_suspended",
|
|
57
|
+
"subscription_terminated",
|
|
58
|
+
"invoice_issued"
|
|
59
|
+
];
|
|
60
|
+
const EmailTemplateSchema = z.enum(EMAIL_TEMPLATES);
|
|
61
|
+
/** Which event triggers which template. Events not listed send no email. */
|
|
62
|
+
const EMAIL_TEMPLATE_BY_EVENT = Object.freeze({
|
|
63
|
+
"subscription.activated": "subscription_started",
|
|
64
|
+
"subscription.renewed": "payment_receipt",
|
|
65
|
+
"subscription.payment_failed": "payment_failed",
|
|
66
|
+
"subscription.cancel_scheduled": "cancel_scheduled",
|
|
67
|
+
"subscription.canceled": "subscription_ended",
|
|
68
|
+
"subscription.expired": "subscription_ended",
|
|
69
|
+
"subscription.suspended": "access_suspended",
|
|
70
|
+
"subscription.terminated": "subscription_terminated",
|
|
71
|
+
"invoice.issued": "invoice_issued"
|
|
72
|
+
});
|
|
73
|
+
function emailTemplateOf(type) {
|
|
74
|
+
return EMAIL_TEMPLATE_BY_EVENT[type] ?? null;
|
|
75
|
+
}
|
|
76
|
+
const EMAIL_LOCALES = ["tr", "en"];
|
|
77
|
+
/** Upper bound of product-provided recipients per account. */
|
|
78
|
+
const MAX_NOTIFICATION_EMAILS = 10;
|
|
79
|
+
const NotificationEmailsSchema = z.array(z.email().max(254)).max(10).transform((emails) => uniqueEmails(emails));
|
|
80
|
+
/** Case-insensitive de-duplication; keeps the first spelling and the order. */
|
|
81
|
+
function uniqueEmails(emails) {
|
|
82
|
+
const seen = /* @__PURE__ */ new Set();
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const email of emails) {
|
|
85
|
+
const key = email.trim().toLowerCase();
|
|
86
|
+
if (key === "" || seen.has(key)) continue;
|
|
87
|
+
seen.add(key);
|
|
88
|
+
out.push(email.trim());
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/** Profile email first, then the account's notification emails. */
|
|
93
|
+
function emailRecipients(profileEmail, notificationEmails) {
|
|
94
|
+
return uniqueEmails([...profileEmail ? [profileEmail] : [], ...notificationEmails]);
|
|
95
|
+
}
|
|
96
|
+
function emailLocaleOf(accountLocale, lastCheckoutLocale, defaultLocale) {
|
|
97
|
+
const known = (value) => EMAIL_LOCALES.includes(value ?? "");
|
|
98
|
+
if (known(accountLocale)) return accountLocale;
|
|
99
|
+
if (known(lastCheckoutLocale)) return lastCheckoutLocale;
|
|
100
|
+
return defaultLocale;
|
|
101
|
+
}
|
|
102
|
+
const ExtraTextValueSchema = z.string().trim().min(1).max(1e3);
|
|
103
|
+
const EmailExtraTextSchema = z.object({
|
|
104
|
+
tr: ExtraTextValueSchema.optional(),
|
|
105
|
+
en: ExtraTextValueSchema.optional()
|
|
106
|
+
});
|
|
107
|
+
const EmailTemplateSettingsSchema = z.object({
|
|
108
|
+
/** false: steward does not send this template (the product sends its own). */
|
|
109
|
+
enabled: z.boolean().default(true),
|
|
110
|
+
/** Plain-text paragraph appended to the template body, per locale. */
|
|
111
|
+
extraText: EmailExtraTextSchema.optional()
|
|
112
|
+
});
|
|
113
|
+
const EmailTemplatesSchema = z.object(Object.fromEntries(EMAIL_TEMPLATES.map((t) => [t, EmailTemplateSettingsSchema.optional()])));
|
|
114
|
+
const EmailSenderSchema = z.object({
|
|
115
|
+
/** Display name; defaults to `branding.name`. */
|
|
116
|
+
name: z.string().trim().min(1).max(100).optional(),
|
|
117
|
+
/** Sender address; defaults to the deployment's `STEWARD_EMAIL_FROM`. Its domain needs SPF/DKIM. */
|
|
118
|
+
address: z.email().max(254).optional()
|
|
119
|
+
});
|
|
120
|
+
/** Placeholders a billing path may use. */
|
|
121
|
+
const BILLING_PATH_PLACEHOLDERS = ["{LOCALE}", "{ACCOUNT_REF}"];
|
|
122
|
+
const BillingPathSchema = z.string().max(500).regex(/^\/(?!\/)[^\s\\]*$/, "an absolute path under appUrl (starts with a single /)").refine((path) => (path.match(/\{[^}]*\}/g) ?? []).every((p) => BILLING_PATH_PLACEHOLDERS.includes(p)), "only {LOCALE} and {ACCOUNT_REF} placeholders");
|
|
123
|
+
const EmailSettingsSchema = z.object({
|
|
124
|
+
from: EmailSenderSchema.optional(),
|
|
125
|
+
/** Reply-To; defaults to `branding.supportEmail`. */
|
|
126
|
+
replyTo: z.email().max(254).optional(),
|
|
127
|
+
/** Product billing page under `appUrl`, e.g. `/{LOCALE}/app/orgs/{ACCOUNT_REF}/billing`; without it buttons open `appUrl`. */
|
|
128
|
+
billingPath: BillingPathSchema.optional(),
|
|
129
|
+
templates: EmailTemplatesSchema.default({})
|
|
130
|
+
});
|
|
131
|
+
const EmailSettingsPatchSchema = z.object({
|
|
132
|
+
from: EmailSenderSchema.strict().nullable().optional(),
|
|
133
|
+
replyTo: z.email().max(254).nullable().optional(),
|
|
134
|
+
billingPath: BillingPathSchema.nullable().optional(),
|
|
135
|
+
templates: z.object(Object.fromEntries(EMAIL_TEMPLATES.map((t) => [t, z.object({
|
|
136
|
+
enabled: z.boolean().optional(),
|
|
137
|
+
extraText: EmailExtraTextSchema.strict().optional()
|
|
138
|
+
}).strict().nullable().optional()]))).strict().optional()
|
|
139
|
+
}).strict();
|
|
140
|
+
/** Pure merge of a validated patch (see the patch rules above). */
|
|
141
|
+
function applyEmailSettingsPatch(current, patch) {
|
|
142
|
+
if (patch === null) return EmailSettingsSchema.parse({});
|
|
143
|
+
const next = {
|
|
144
|
+
...current,
|
|
145
|
+
templates: { ...current.templates }
|
|
146
|
+
};
|
|
147
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
148
|
+
if (value === void 0) continue;
|
|
149
|
+
if (key === "templates") {
|
|
150
|
+
const templates = next.templates;
|
|
151
|
+
for (const [template, settings] of Object.entries(value)) {
|
|
152
|
+
if (settings === void 0) continue;
|
|
153
|
+
if (settings === null) delete templates[template];
|
|
154
|
+
else templates[template] = settings;
|
|
155
|
+
}
|
|
156
|
+
} else if (value === null) delete next[key];
|
|
157
|
+
else next[key] = value;
|
|
158
|
+
}
|
|
159
|
+
return EmailSettingsSchema.parse(next);
|
|
160
|
+
}
|
|
161
|
+
function emailTemplateSettings(settings, template) {
|
|
162
|
+
return settings.templates[template] ?? { enabled: true };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Button target: `appUrl` + `billingPath` with placeholders filled (the ref is URL-encoded).
|
|
166
|
+
* Null without `appUrl`, or when the result would leave the `appUrl` origin.
|
|
167
|
+
*/
|
|
168
|
+
function billingPageUrl(appUrl, billingPath, locale, accountRef) {
|
|
169
|
+
if (appUrl === void 0) return null;
|
|
170
|
+
let base;
|
|
171
|
+
try {
|
|
172
|
+
base = new URL(appUrl);
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
if (billingPath === void 0) return base.toString();
|
|
177
|
+
const path = billingPath.replaceAll("{LOCALE}", locale).replaceAll("{ACCOUNT_REF}", encodeURIComponent(accountRef));
|
|
178
|
+
try {
|
|
179
|
+
const url = new URL(path, base.origin);
|
|
180
|
+
return url.origin === base.origin ? url.toString() : null;
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Whether the email still describes the account at send time. Receipts, the welcome email
|
|
187
|
+
* (legal confirmation) and the final termination notice always go; the others only while
|
|
188
|
+
* the situation they describe still holds.
|
|
189
|
+
*/
|
|
190
|
+
function isEmailRelevant(template, facts) {
|
|
191
|
+
const live = facts.state.subscription;
|
|
192
|
+
const same = live !== null && live.id === facts.subscriptionId;
|
|
193
|
+
switch (template) {
|
|
194
|
+
case "subscription_started":
|
|
195
|
+
case "payment_receipt":
|
|
196
|
+
case "subscription_terminated": return true;
|
|
197
|
+
case "payment_failed": return same && live.status === "past_due";
|
|
198
|
+
case "cancel_scheduled": return same && live.cancelAtPeriodEnd;
|
|
199
|
+
case "subscription_ended": return live === null;
|
|
200
|
+
case "access_suspended": return same && live.status === "suspended";
|
|
201
|
+
case "invoice_issued": return facts.invoiceStatus === "issued";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
49
205
|
//#region ../contract/src/accounts.ts
|
|
206
|
+
const AccountLocaleSchema = z.enum(EMAIL_LOCALES);
|
|
50
207
|
const AccountUpsertInputSchema = z.object({
|
|
51
208
|
displayName: z.string().min(1).max(200),
|
|
52
|
-
metadata: MetadataSchema.default({})
|
|
209
|
+
metadata: MetadataSchema.default({}),
|
|
210
|
+
/**
|
|
211
|
+
* Extra recipients of billing emails besides the billing profile email (e.g. the
|
|
212
|
+
* organization owners). Omitted: unchanged (older clients keep working); `[]` clears.
|
|
213
|
+
*/
|
|
214
|
+
notificationEmails: NotificationEmailsSchema.optional(),
|
|
215
|
+
/** Language of billing emails. Omitted: unchanged; `null`: latest checkout's, else the product default. */
|
|
216
|
+
locale: AccountLocaleSchema.nullable().optional()
|
|
53
217
|
});
|
|
54
218
|
const BillingProfileKindSchema = z.enum(["individual", "company"]);
|
|
55
219
|
const BillingProfileSchema = z.object({
|
|
@@ -162,7 +326,11 @@ const AccountSchema = z.object({
|
|
|
162
326
|
displayName: z.string(),
|
|
163
327
|
metadata: MetadataSchema,
|
|
164
328
|
createdAt: DateTimeSchema,
|
|
165
|
-
snapshot: EntitlementSnapshotSchema
|
|
329
|
+
snapshot: EntitlementSnapshotSchema,
|
|
330
|
+
/** Extra billing email recipients (older steward: absent → `[]`). */
|
|
331
|
+
notificationEmails: z.array(z.string()).default([]),
|
|
332
|
+
/** Billing email language set by the product; null: not set. */
|
|
333
|
+
locale: AccountLocaleSchema.nullable().default(null)
|
|
166
334
|
});
|
|
167
335
|
const AccountDeleteResponseSchema = z.object({
|
|
168
336
|
ref: ExternalRefSchema,
|
|
@@ -347,4 +515,4 @@ const PingSchema = z.object({
|
|
|
347
515
|
endpointId: IdSchema
|
|
348
516
|
});
|
|
349
517
|
//#endregion
|
|
350
|
-
export {
|
|
518
|
+
export { CurrencySchema as $, EMAIL_LOCALES as A, NotificationEmailsSchema as B, BillingProfileKindSchema as C, SubscriptionSummarySchema as D, SubscriptionStatusSchema as E, EmailSettingsPatchSchema as F, emailTemplateOf as G, billingPageUrl as H, EmailSettingsSchema as I, uniqueEmails as J, emailTemplateSettings as K, EmailTemplateSchema as L, EMAIL_TEMPLATE_BY_EVENT as M, EmailExtraTextSchema as N, BILLING_PATH_PLACEHOLDERS as O, EmailSenderSchema as P, CodeSchema as Q, EmailTemplateSettingsSchema as R, AccountUpsertInputSchema as S, EntitlementSnapshotSchema as T, emailLocaleOf as U, applyEmailSettingsPatch as V, emailRecipients as W, ActorRefSchema as X, API_VERSION as Y, AmountMinorSchema as Z, AccountProfileStateSchema as _, EVENT_TYPES as a, ErrorResponseSchema as at, AccountStateSchema as b, PING_EVENT_TYPE as c, IntervalSchema as ct, ACCOUNT_INCLUDES as d, DateTimeSchema as et, AccessStateSchema as f, AccountOpenCheckoutSchema as g, AccountLocaleSchema as h, EVENT_DETAIL_SCHEMAS as i, ErrorCategorySchema as it, EMAIL_TEMPLATES as j, BillingPathSchema as k, PingSchema as l, MetadataSchema as lt, AccountDunningStateSchema as m, ApiVersionSchema as n, EntitlementValueSchema as nt, EventIdSchema as o, ExternalRefSchema as ot, AccountDeleteResponseSchema as p, isEmailRelevant as q, BillingEventSchema as r, EntitlementsSchema as rt, LEGACY_EVENT_TYPES as s, IdSchema as st, AnyBillingEventSchema as t, ERROR_CATEGORIES as tt, isEventType as u, AccountScheduledChangeSchema as v, BillingProfileSchema as w, AccountSubscriptionStateSchema as x, AccountSchema as y, MAX_NOTIFICATION_EMAILS as z };
|