@stewardhq/sdk 0.1.0-rc.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/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
- import { C as BillingProfileSchema, D as API_VERSION, O as ActorRefSchema, R as ExternalRefSchema, a as EVENT_TYPES, d as ACCOUNT_INCLUDES, i as EVENT_DETAIL_SCHEMAS, r as BillingEventSchema, s as LEGACY_EVENT_TYPES, x as AccountUpsertInputSchema, z as IdSchema } from "./_chunks/events.js";
2
- import { A as webhookEndpointUrlProblem, Gt as applySettingsPatch, Kt as isReturnUrlAllowed, L as ERROR_CODES, M as PlanSetInputSchema, Mt as checkoutRedirectUrl, Nt as isLegacyCheckoutInput, Q as HEADER_REQUEST_ID, Rt as ProductSettingsPatchSchema, S as WebhookEndpointCreateInputSchema, Tt as CheckoutSessionCreateInputSchema, Xt as CatalogSyncInputSchema, Y as HEADER_ACTOR_REF, Z as HEADER_IDEMPOTENCY_KEY, bt as PortalSessionCreateInputSchema, dt as manualInvoiceTotals, ft as splitInclusiveTax, h as buildDoctorReport, lt as ManualInvoiceInputSchema, mt as GrantCreateInputSchema, pt as CancelSubscriptionInputSchema, rt as InvoiceIssueInputSchema, st as InvoiceVoidInputSchema, v as DEFAULT_ENDPOINT_EVENT_TYPES, w as WebhookEndpointRotateInputSchema, zt as ProductSettingsSchema } from "./_chunks/src.js";
1
+ import { G as emailTemplateOf, K as emailTemplateSettings, S as AccountUpsertInputSchema, U as emailLocaleOf, W as emailRecipients, X as ActorRefSchema, Y as API_VERSION, a as EVENT_TYPES, d as ACCOUNT_INCLUDES, i as EVENT_DETAIL_SCHEMAS, ot as ExternalRefSchema, q as isEmailRelevant, r as BillingEventSchema, s as LEGACY_EVENT_TYPES, st as IdSchema, w as BillingProfileSchema } from "./_chunks/events.js";
2
+ import { At as checkoutRedirectUrl, Ct as CheckoutSessionCreateInputSchema, E as WebhookEndpointRotateInputSchema, Ft as ProductSettingsPatchSchema, Ht as applySettingsPatch, It as ProductSettingsSchema, M as webhookEndpointUrlProblem, P as PlanSetInputSchema, Ut as isReturnUrlAllowed, X as HEADER_REQUEST_ID, Y as HEADER_IDEMPOTENCY_KEY, at as InvoiceVoidInputSchema, b as DEFAULT_ENDPOINT_EVENT_TYPES, dt as CancelSubscriptionInputSchema, ft as GrantCreateInputSchema, g as buildDoctorReport, lt as manualInvoiceTotals, q as HEADER_ACTOR_REF, qt as CatalogSyncInputSchema, st as ManualInvoiceInputSchema, tt as InvoiceIssueInputSchema, ut as splitInclusiveTax, vt as PortalSessionCreateInputSchema, w as WebhookEndpointCreateInputSchema, z as ERROR_CODES } from "./_chunks/src.js";
3
3
  import { i as LIVE_SUBSCRIPTION_STATUSES, r as isValidVkn, s as resolveEntitlements, t as isE164 } from "./_chunks/validators.js";
4
4
  import { t as createSteward } from "./_chunks/steward.js";
5
5
  import { n as signWebhook } from "./_chunks/webhook.js";
@@ -54,6 +54,7 @@ function createWorld(options) {
54
54
  invoices: [],
55
55
  grants: [],
56
56
  events: [],
57
+ emails: [],
57
58
  now,
58
59
  setNow: (ms) => {
59
60
  const current = now();
@@ -297,8 +298,33 @@ function emitEvent(w, ref, state, event) {
297
298
  deliveredAt: null
298
299
  } : null
299
300
  });
301
+ sendEmail(w, ref, state, event, id);
300
302
  return id;
301
303
  }
304
+ /** The service's email/sender.ts rules without transport (skipped emails are not recorded). */
305
+ function sendEmail(w, ref, state, event, eventId) {
306
+ const template = emailTemplateOf(event.type);
307
+ const row = w.accounts.get(ref);
308
+ if (template === null || !row || row.deletedAt !== void 0) return;
309
+ if (!emailTemplateSettings(w.settings.emails, template).enabled) return;
310
+ const detail = event.detail;
311
+ if (!isEmailRelevant(template, {
312
+ state,
313
+ subscriptionId: detail.subscriptionId ?? null,
314
+ invoiceStatus: "issued"
315
+ })) return;
316
+ const to = emailRecipients(row.profile?.email, row.notificationEmails);
317
+ if (to.length === 0) return;
318
+ const lastSession = w.sessions.filter((s) => s.accountRef === ref).sort((a, b) => b.createdAt - a.createdAt)[0];
319
+ w.emails.push({
320
+ eventId,
321
+ template,
322
+ accountRef: ref,
323
+ to,
324
+ locale: emailLocaleOf(row.locale, lastSession?.locale, w.settings.defaultLocale),
325
+ sentAt: w.now()
326
+ });
327
+ }
302
328
  const SNAPSHOT_KEYS = [
303
329
  "planCode",
304
330
  "accessState",
@@ -404,8 +430,7 @@ function toCheckoutSession(w, row) {
404
430
  currency: price.currency,
405
431
  provider: "fake",
406
432
  locale: row.locale,
407
- returnUrl: row.returnUrl,
408
- successUrl: row.successUrl ?? row.returnUrl,
433
+ successUrl: row.successUrl,
409
434
  cancelUrl: row.cancelUrl,
410
435
  consents: row.consents,
411
436
  actorRef: row.actorRef,
@@ -576,7 +601,9 @@ function toAccount(row) {
576
601
  displayName: row.displayName,
577
602
  metadata: row.metadata,
578
603
  createdAt: iso(row.createdAt),
579
- snapshot: toSnapshot(stateOf(row))
604
+ snapshot: toSnapshot(stateOf(row)),
605
+ notificationEmails: [...row.notificationEmails],
606
+ locale: row.locale
580
607
  };
581
608
  }
582
609
  function stateOf(row) {
@@ -595,13 +622,17 @@ function upsertAccount(w, ref, input) {
595
622
  version: 0,
596
623
  state: null,
597
624
  history: [],
598
- profile: null
625
+ profile: null,
626
+ notificationEmails: input.notificationEmails ?? [],
627
+ locale: input.locale ?? null
599
628
  };
600
629
  computeState(w, row, w.now());
601
630
  w.accounts.set(ref, row);
602
631
  } else {
603
632
  row.displayName = input.displayName;
604
633
  row.metadata = input.metadata;
634
+ if (input.notificationEmails !== void 0) row.notificationEmails = input.notificationEmails;
635
+ if (input.locale !== void 0) row.locale = input.locale;
605
636
  }
606
637
  const { state } = commitAccountChange(w, ref, "account.upserted");
607
638
  return {
@@ -630,16 +661,6 @@ function saveProfile(row, profile) {
630
661
  row.profile = saved;
631
662
  return saved;
632
663
  }
633
- function putBillingProfile(w, ref, profile) {
634
- const row = w.accounts.get(ref);
635
- if (!row) return fail("account_not_found");
636
- const saved = saveProfile(row, profile);
637
- const { state } = commitAccountChange(w, ref, "billing_profile.updated");
638
- return ok({
639
- ...saved,
640
- account: state
641
- });
642
- }
643
664
  function accountDetail(w, ref, include) {
644
665
  const row = w.accounts.get(ref);
645
666
  if (!row) return null;
@@ -668,11 +689,8 @@ function accountDetail(w, ref, include) {
668
689
  if (want.has("invoices")) detail.invoices = newest(w.invoices.filter((i) => i.accountRef === ref)).map(toInvoice);
669
690
  return detail;
670
691
  }
671
- /** Sahte sağlayıcının ödeme sayfası (servisin `providers/fake`); eski gövdenin `providerPayload`'ı. */
672
- const providerPageUrl = (baseUrl, token) => `${baseUrl}/public/v1/providers/fake/checkout/${token}`;
673
692
  /** Hosted checkout sayfası (servisin `hostedCheckoutUrl`); sahtede `hosted.ts` sunar. */
674
693
  const hostedPageUrl = (baseUrl, id, pageKey) => `${baseUrl}/public/v1/checkout/${id}?k=${pageKey}`;
675
- const FAKE_PROVIDER_TOKEN_TTL_MS = 18e5;
676
694
  const PAGE_KEY_BYTES = 32;
677
695
  /** Servis: `appUrl`'in origin'i + `extraReturnOrigins` (sahte, ayarlanmadıysa her adresi kabul eder). */
678
696
  function returnUrlAllowed(w, url) {
@@ -687,56 +705,12 @@ function randomPageKey() {
687
705
  const bytes = globalThis.crypto.getRandomValues(new Uint8Array(PAGE_KEY_BYTES));
688
706
  return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
689
707
  }
690
- function createCheckoutSession(w, baseUrl, ref, input, actorRef) {
691
- const row = w.accounts.get(ref);
692
- if (!row) return fail("account_not_found");
693
- const { settings } = w;
694
- if (!returnUrlAllowed(w, input.successUrl)) return fail("return_url_not_allowed");
695
- if (liveSubscription(w, ref)) return fail("already_subscribed");
696
- const price = sellablePrice(w, input.planCode, input.interval, input.currency);
697
- if (!price) return fail("plan_not_sellable");
698
- const now = w.now();
699
- saveProfile(row, input.profile);
700
- const token = `fake_tok_${globalThis.crypto.randomUUID()}`;
701
- const expiresAt = Math.min(now + settings.checkoutTtlMinutes * 6e4, now + FAKE_PROVIDER_TOKEN_TTL_MS);
702
- const session = {
703
- id: w.id(),
704
- accountRef: ref,
705
- priceId: price.id,
706
- status: "open",
707
- locale: input.locale,
708
- returnUrl: input.successUrl,
709
- successUrl: null,
710
- cancelUrl: null,
711
- consentDocs: [],
712
- prefill: null,
713
- pageKey: null,
714
- consents: input.consents,
715
- actorRef,
716
- failureReason: null,
717
- subscriptionId: null,
718
- expiresAt,
719
- createdAt: now,
720
- completedAt: null,
721
- providerToken: token
722
- };
723
- w.sessions.push(session);
724
- commitAccountChange(w, ref, "checkout.opened");
725
- return ok({
726
- ...toCheckoutSession(w, session),
727
- url: null,
728
- providerPayload: {
729
- type: "redirect",
730
- url: providerPageUrl(baseUrl, token)
731
- }
732
- });
733
- }
734
708
  /**
735
- * `createHostedCheckoutSession` (hosted gövde, F2b): sağlayıcıya gidilmez; doğrulama sırası ve
736
- * kodları servisle aynı (hesap, dönüş origin'leri, canlı abonelik, satılabilir fiyat). Hesap
737
- * yoksa `displayName` ile açılır. `url` sahte steward'ın hosted sayfasıdır (`hosted.ts`).
709
+ * `createHostedCheckoutSession` (service): no provider call; validation order and codes match the
710
+ * service (account, return origins, live subscription, sellable price). A missing account is
711
+ * opened with `displayName`. `url` is the fake steward's hosted page (`hosted.ts`).
738
712
  */
739
- function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
713
+ function createCheckoutSession(w, baseUrl, ref, input, actorRef) {
740
714
  const existing = w.accounts.get(ref);
741
715
  if (!existing && input.displayName === void 0) return fail("account_not_found");
742
716
  const { settings } = w;
@@ -757,7 +731,6 @@ function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
757
731
  priceId: price.id,
758
732
  status: "open",
759
733
  locale: input.locale ?? settings.defaultLocale,
760
- returnUrl: input.successUrl,
761
734
  successUrl: input.successUrl,
762
735
  cancelUrl: input.cancelUrl ?? null,
763
736
  consentDocs: (input.consents ?? []).map(({ document, version, url }) => ({
@@ -773,8 +746,7 @@ function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
773
746
  subscriptionId: null,
774
747
  expiresAt: now + settings.checkoutTtlMinutes * 6e4,
775
748
  createdAt: now,
776
- completedAt: null,
777
- providerToken: null
749
+ completedAt: null
778
750
  };
779
751
  w.sessions.push(session);
780
752
  const { state } = commitAccountChange(w, ref, "checkout.opened");
@@ -831,11 +803,10 @@ function hostedProfile(row, prefill) {
831
803
  * Hosted oturum (F2b): sahte sayfada `details` adımı yok. Ödeme sonucundan önce servisteki
832
804
  * gönderimin izi yazılır: hesabın profili yoksa ön doldurmadan sahte profil (varsa aynen
833
805
  * kalır: sayfa saklı profili ön doldurur), belgelerin onay izi, `billing_profile.updated`.
834
- * Eski gövdede no-op. Hesap bu arada abone olduysa servis `details`'te oturumu kapatır;
806
+ * Hesap bu arada abone olduysa servis `details`'te oturumu kapatır;
835
807
  * çağıranlar o durumda bunu çağırmaz.
836
808
  */
837
809
  function submitHostedDetails(w, session) {
838
- if (session.successUrl === null) return;
839
810
  const row = account(w, session.accountRef);
840
811
  if (!row.profile) saveProfile(row, hostedProfile(row, session.prefill));
841
812
  const acceptedAt = iso(w.now());
@@ -1239,8 +1210,9 @@ function resyncAccount(w, ref) {
1239
1210
  }
1240
1211
  /**
1241
1212
  * `deleteAccount` (`domain/account-deletion.ts`, F4e): önkoşullar sırayla (hesap yok 404,
1242
- * silinmiş 410, canlı abonelik, formu açık checkout — sahte steward'da yalnızca eski
1243
- * gövdeli oturumun formu vardır) ve hiçbiri tutmazsa değişiklik yok. Sonra anonimleştirme:
1213
+ * silinmiş 410, canlı abonelik) ve hiçbiri tutmazsa değişiklik yok. The service's
1214
+ * `checkout_in_progress` (open provider form) never happens here: the fake hosted page has no
1215
+ * provider form, so open sessions are formless and deletion closes them. Sonra anonimleştirme:
1244
1216
  * ad/metadata/profil, oturumların ön doldurması/anahtarı ve onay izinin `ipHash`/`userAgent`'ı,
1245
1217
  * portal oturumları, aktif grant'ler; eski event yüklerinde `displayName`. Faturalar kalır.
1246
1218
  */
@@ -1250,8 +1222,6 @@ function deleteAccount(w, ref, actorRef) {
1250
1222
  if (row.deletedAt !== void 0) return fail("account_deleted");
1251
1223
  const live = liveSubscription(w, ref);
1252
1224
  if (live) return fail("subscription_active", `abonelik ${live.id} canlı; önce hemen iptal edin (mode: immediate)`);
1253
- const withForm = w.sessions.find((s) => s.accountRef === ref && s.status === "open" && s.providerToken !== null);
1254
- if (withForm) return fail("checkout_in_progress", `checkout ${withForm.id} sağlayıcı formu açık; süresi ${iso(withForm.expiresAt)} sonrasında dolar, kapanınca tekrar deneyin`);
1255
1225
  const now = w.now();
1256
1226
  for (const session of w.sessions.filter((s) => s.accountRef === ref)) {
1257
1227
  if (session.status === "open") Object.assign(session, {
@@ -1271,6 +1241,8 @@ function deleteAccount(w, ref, actorRef) {
1271
1241
  displayName: "",
1272
1242
  metadata: {},
1273
1243
  profile: null,
1244
+ notificationEmails: [],
1245
+ locale: null,
1274
1246
  deletedAt: now
1275
1247
  });
1276
1248
  for (const event of w.events) {
@@ -1501,33 +1473,10 @@ function portalInvoiceDocument(w, session, invoiceId) {
1501
1473
  filename: `${safe === "" ? "invoice" : safe}.pdf`
1502
1474
  };
1503
1475
  }
1504
- function toRecord(w, event) {
1505
- const d = event.delivery;
1506
- return {
1507
- event: event.payload,
1508
- deliveries: d && w.endpoint ? [{
1509
- endpointId: w.endpoint.id,
1510
- status: d.status,
1511
- attempts: d.attempts,
1512
- lastHttpStatus: d.lastHttpStatus,
1513
- lastError: d.lastError,
1514
- nextAttemptAt: d.status === "pending" ? iso(d.nextAttemptAt) : null,
1515
- deliveredAt: isoOrNull(d.deliveredAt)
1516
- }] : []
1517
- };
1518
- }
1519
- function listEvents(w, query) {
1520
- if (query.accountRef !== void 0 && !w.accounts.has(query.accountRef)) return fail("account_not_found");
1521
- const rows = w.events.filter((e) => (query.accountRef === void 0 || e.accountRef === query.accountRef) && (query.cursor === void 0 || e.id < query.cursor)).sort((a, b) => a.id < b.id ? 1 : -1);
1522
- const page = rows.slice(0, query.limit);
1523
- return ok({
1524
- items: page.map((e) => toRecord(w, e)),
1525
- nextCursor: rows.length > query.limit ? page.at(-1).id : null
1526
- });
1527
- }
1476
+ /** Requeues the event's delivery (dead or delivered included); `false` if there is no such event. */
1528
1477
  function redeliverEvent(w, id) {
1529
1478
  const event = w.events.find((e) => e.id === id);
1530
- if (!event) return null;
1479
+ if (!event) return false;
1531
1480
  if (event.delivery) Object.assign(event.delivery, {
1532
1481
  status: "pending",
1533
1482
  attempts: 0,
@@ -1535,7 +1484,7 @@ function redeliverEvent(w, id) {
1535
1484
  lastError: null,
1536
1485
  deliveredAt: null
1537
1486
  });
1538
- return toRecord(w, event);
1487
+ return true;
1539
1488
  }
1540
1489
  /**
1541
1490
  * Tahsilat sonucu. Dunning'deki abonelikte AYNI başarısız siparişin yeniden
@@ -1713,17 +1662,16 @@ function expireScheduledCancellations(w) {
1713
1662
  }
1714
1663
  }
1715
1664
  /**
1716
- * Süresi dolan açık oturum: sahte sağlayıcıda ödenmemiş form (`fake:not_completed`).
1665
+ * Expired open session (formless: the fake hosted page has no provider form, so `expired`).
1717
1666
  * Servis gibi kapanan oturumun `expiresAt`'i job kirasının sonu (o an + 5 dk) kalır.
1718
1667
  */
1719
1668
  function expireCheckoutSessions(w) {
1720
1669
  const now = w.now();
1721
1670
  for (const session of w.sessions) {
1722
1671
  if (session.status !== "open" || session.expiresAt > now) continue;
1723
- const failureReason = session.providerToken === null ? "expired" : "expired:fake:not_completed";
1724
1672
  Object.assign(session, {
1725
1673
  status: "expired",
1726
- failureReason,
1674
+ failureReason: "expired",
1727
1675
  completedAt: now,
1728
1676
  expiresAt: now + 3e5,
1729
1677
  ...CLOSED_SESSION_CLEANUP
@@ -1977,7 +1925,11 @@ function doctorReport(w) {
1977
1925
  oldestPendingAt: null,
1978
1926
  lastDelivery: null
1979
1927
  })),
1980
- dunning: { dryRun: w.dunningPolicy.dryRun }
1928
+ dunning: { dryRun: w.dunningPolicy.dryRun },
1929
+ emailTransport: {
1930
+ configured: true,
1931
+ defaultFromConfigured: true
1932
+ }
1981
1933
  });
1982
1934
  }
1983
1935
  const cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
@@ -2080,7 +2032,6 @@ const MUTATING = /* @__PURE__ */ new Set([
2080
2032
  "DELETE"
2081
2033
  ]);
2082
2034
  const KEY_RE = /^[A-Za-z0-9_.:-]{8,200}$/;
2083
- const EVENT_ID = /^evt_[0-9a-f-]{36}$/;
2084
2035
  const json = (body, status = 200, headers = {}) => ({
2085
2036
  status,
2086
2037
  body: JSON.stringify(body),
@@ -2206,23 +2157,6 @@ function routes(w, config) {
2206
2157
  if (notModified(ctx.headers.get("if-none-match"), etag)) return empty(304, { etag });
2207
2158
  return json(state, 200, { etag });
2208
2159
  }),
2209
- route("GET", "accounts/:ref/entitlements", (ctx) => {
2210
- const accountRef = ref(ctx);
2211
- if (!accountRef) return ctx.error("invalid_account_ref");
2212
- if (!w.accounts.has(accountRef)) return ctx.error("account_not_found");
2213
- const snapshot = toSnapshot(storedState(w, accountRef));
2214
- const etag = `"${snapshot.version}"`;
2215
- if (notModified(ctx.headers.get("if-none-match"), etag)) return empty(304, { etag });
2216
- return json(snapshot, 200, { etag });
2217
- }),
2218
- route("PUT", "accounts/:ref/billing-profile", (ctx) => {
2219
- const accountRef = ref(ctx);
2220
- if (!accountRef) return ctx.error("invalid_account_ref");
2221
- const parsed = parseBody(ctx, BillingProfileSchema);
2222
- if (!parsed.ok) return parsed.reply;
2223
- const result = putBillingProfile(w, accountRef, parsed.data);
2224
- return result.ok ? json(result.value) : failure(ctx, result);
2225
- }),
2226
2160
  route("PUT", "accounts/:ref/plan", (ctx) => {
2227
2161
  const accountRef = ref(ctx);
2228
2162
  if (!accountRef) return ctx.error("invalid_account_ref");
@@ -2242,7 +2176,7 @@ function routes(w, config) {
2242
2176
  if (!accountRef) return ctx.error("invalid_account_ref");
2243
2177
  const parsed = parseBody(ctx, CheckoutSessionCreateInputSchema);
2244
2178
  if (!parsed.ok) return parsed.reply;
2245
- const result = isLegacyCheckoutInput(parsed.data) ? createCheckoutSession(w, config.baseUrl, accountRef, parsed.data, ctx.actorRef) : createHostedCheckoutSession(w, config.baseUrl, accountRef, parsed.data, ctx.actorRef);
2179
+ const result = createCheckoutSession(w, config.baseUrl, accountRef, parsed.data, ctx.actorRef);
2246
2180
  return result.ok ? json(result.value, 201) : failure(ctx, result);
2247
2181
  }),
2248
2182
  route("POST", "accounts/:ref/portal-sessions", (ctx) => {
@@ -2314,25 +2248,6 @@ function routes(w, config) {
2314
2248
  "x-content-sha256": sha256
2315
2249
  }
2316
2250
  };
2317
- }),
2318
- route("GET", "events", (ctx) => {
2319
- const query = ctx.url.searchParams;
2320
- const accountRef = query.get("account") ?? void 0;
2321
- const cursor = query.get("cursor") ?? void 0;
2322
- const limit = Number(query.get("limit") ?? 50);
2323
- if (accountRef !== void 0 && !ExternalRefSchema.safeParse(accountRef).success) return ctx.error("invalid_account_ref");
2324
- if (cursor !== void 0 && !EVENT_ID.test(cursor)) return ctx.error("invalid_cursor");
2325
- if (!Number.isInteger(limit) || limit < 1 || limit > 200) return ctx.error("invalid_limit", "1–200");
2326
- const result = listEvents(w, {
2327
- ...accountRef !== void 0 ? { accountRef } : {},
2328
- ...cursor !== void 0 ? { cursor } : {},
2329
- limit
2330
- });
2331
- return result.ok ? json(result.value) : failure(ctx, result);
2332
- }),
2333
- route("POST", "events/:id/redeliver", (ctx) => {
2334
- const record = EVENT_ID.test(ctx.params.id ?? "") ? redeliverEvent(w, ctx.params.id) : null;
2335
- return record ? json(record) : ctx.error("event_not_found");
2336
2251
  })
2337
2252
  ];
2338
2253
  }
@@ -3679,7 +3594,6 @@ function createFakeSteward(options = {}) {
3679
3594
  },
3680
3595
  async checkoutCanceled(sessionId) {
3681
3596
  const row = sessionRow(w, sessionId);
3682
- if (row.successUrl === null) throw new Error(`simulate.checkoutCanceled: ${sessionId} eski gövdeli oturum (vazgeç yalnızca hosted sayfada)`);
3683
3597
  cancelCheckout(w, row);
3684
3598
  return checkoutSessionResponse(w, sessionId);
3685
3599
  },
@@ -3744,6 +3658,9 @@ function createFakeSteward(options = {}) {
3744
3658
  out.push(delivery);
3745
3659
  }
3746
3660
  return out;
3661
+ },
3662
+ redeliver(eventId) {
3663
+ if (!redeliverEvent(w, eventId)) throw new Error(`simulate.redeliver: unknown event ${eventId}`);
3747
3664
  }
3748
3665
  },
3749
3666
  connect(next) {
@@ -3752,6 +3669,14 @@ function createFakeSteward(options = {}) {
3752
3669
  handler = next;
3753
3670
  },
3754
3671
  events: (ref) => w.events.filter((e) => ref === void 0 || e.accountRef === ref).map((e) => e.payload),
3672
+ emails: (ref) => w.emails.filter((e) => ref === void 0 || e.accountRef === ref).map((e) => ({
3673
+ eventId: e.eventId,
3674
+ template: e.template,
3675
+ accountRef: e.accountRef,
3676
+ to: [...e.to],
3677
+ locale: e.locale,
3678
+ sentAt: new Date(e.sentAt).toISOString()
3679
+ })),
3755
3680
  now: () => new Date(w.now())
3756
3681
  };
3757
3682
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stewardhq/sdk",
3
- "version": "0.1.0-rc.0",
3
+ "version": "0.3.0",
4
4
  "description": "Steward SDK: contract schemas, typed API client and webhook signing.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -45,8 +45,7 @@
45
45
  ],
46
46
  "publishConfig": {
47
47
  "access": "public",
48
- "registry": "https://registry.npmjs.org",
49
- "provenance": true
48
+ "registry": "https://registry.npmjs.org"
50
49
  },
51
50
  "dependencies": {
52
51
  "zod": "^4.0.0"
@@ -55,9 +54,9 @@
55
54
  "@types/node": "^24.13.3",
56
55
  "tsdown": "^0.23.0",
57
56
  "typescript": "^5.9.0",
58
- "vitest": "^3.0.0",
57
+ "vitest": "^5.0.1",
59
58
  "workerd": "^1.20260916.1",
60
- "@z9cloud/steward-contract": "0.2.0"
59
+ "@steward/contract": "0.2.0"
61
60
  },
62
61
  "scripts": {
63
62
  "build": "tsdown && node scripts/check-dist.mjs",
@@ -1,17 +0,0 @@
1
- //#region src/locale.ts
2
- /** `tr`, `tr-TR` → tr; diğer her dil → en. */
3
- function localeOf(locale) {
4
- return locale.toLowerCase().startsWith("tr") ? "tr" : "en";
5
- }
6
- /**
7
- * Hosted sayfa dili (`Checkout`, `CustomerPortal`): BCP-47 benzeri etiketin birincil alt etiketi
8
- * (`tr`, `tr-TR`, `EN_us` → tr/en). Desteklenmeyen dil ya da boş değer → `undefined`: çağıran
9
- * dili göndermez, steward ürün ayarı `defaultLocale`'i kullanır (ürünün dil algısı sayfayı bozmasın).
10
- */
11
- function hostedLocaleOf(locale) {
12
- if (typeof locale !== "string") return void 0;
13
- const primary = locale.trim().toLowerCase().split(/[-_]/)[0];
14
- return primary === "tr" || primary === "en" ? primary : void 0;
15
- }
16
- //#endregion
17
- export { localeOf as n, hostedLocaleOf as t };