@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 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. Endpoint `webhook`
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`, npm provenance ile).
431
+ (`.github/workflows/sdk-publish.yml`).
@@ -1,4 +1,4 @@
1
- import { R as errorCodeSpec } from "./src.js";
1
+ import { B as errorCodeSpec } from "./src.js";
2
2
  //#region src/errors.ts
3
3
  var StewardError = class extends Error {
4
4
  code;
@@ -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 { AccountProfileState as A, AccountUpsertInputSchema as B, AccountDeleteResponse as C, AccountInclude as D, AccountDunningStateSchema as E, AccountState as F, EntitlementSnapshot as G, BillingProfileInput as H, AccountStateSchema as I, SubscriptionStatusSchema as J, EntitlementSnapshotSchema as K, AccountSubscriptionState as L, AccountScheduledChange as M, AccountScheduledChangeSchema as N, AccountOpenCheckout as O, AccountSchema as P, AccountSubscriptionStateSchema as R, Account as S, AccountDunningState as T, BillingProfileKindSchema as U, BillingProfile as V, BillingProfileSchema as W, SubscriptionSummarySchema as X, SubscriptionSummary as Y, 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, AccountProfileStateSchema as j, AccountOpenCheckoutSchema as k, EVENT_TYPES as l, LegacyEventType as m, AnyBillingEvent as n, BillingEventOf as o, LEGACY_EVENT_TYPES as p, SubscriptionStatus 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, AccountUpsertInput as z };
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 };
@@ -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 { CodeSchema as A, IntervalSchema as B, BillingProfileSchema as C, API_VERSION as D, SubscriptionSummarySchema as E, EntitlementsSchema as F, ErrorCategorySchema as I, ErrorResponseSchema as L, DateTimeSchema as M, ERROR_CATEGORIES as N, ActorRefSchema as O, EntitlementValueSchema as P, ExternalRefSchema as R, BillingProfileKindSchema as S, SubscriptionStatusSchema as T, MetadataSchema as V, AccountScheduledChangeSchema as _, EVENT_TYPES as a, AccountSubscriptionStateSchema as b, PING_EVENT_TYPE as c, ACCOUNT_INCLUDES as d, AccessStateSchema as f, AccountProfileStateSchema as g, AccountOpenCheckoutSchema as h, EVENT_DETAIL_SCHEMAS as i, CurrencySchema as j, AmountMinorSchema as k, PingSchema as l, AccountDunningStateSchema as m, ApiVersionSchema as n, EventIdSchema as o, AccountDeleteResponseSchema as p, BillingEventSchema as r, LEGACY_EVENT_TYPES as s, AnyBillingEventSchema as t, isEventType as u, AccountSchema as v, EntitlementSnapshotSchema as w, AccountUpsertInputSchema as x, AccountStateSchema as y, IdSchema as z };
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 };