@stewardhq/sdk 0.1.0-rc.0 → 0.2.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
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";
2
+ import { $ as InvoiceIssueInputSchema, A as webhookEndpointUrlProblem, Bt as applySettingsPatch, G as HEADER_ACTOR_REF, Gt as CatalogSyncInputSchema, J as HEADER_REQUEST_ID, L as ERROR_CODES, M as PlanSetInputSchema, Nt as ProductSettingsPatchSchema, Ot as checkoutRedirectUrl, Pt as ProductSettingsSchema, S as WebhookEndpointCreateInputSchema, Vt as isReturnUrlAllowed, at as ManualInvoiceInputSchema, ct as splitInclusiveTax, gt as PortalSessionCreateInputSchema, h as buildDoctorReport, lt as CancelSubscriptionInputSchema, q as HEADER_IDEMPOTENCY_KEY, rt as InvoiceVoidInputSchema, st as manualInvoiceTotals, ut as GrantCreateInputSchema, v as DEFAULT_ENDPOINT_EVENT_TYPES, w as WebhookEndpointRotateInputSchema, xt as CheckoutSessionCreateInputSchema } 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";
@@ -404,8 +404,7 @@ function toCheckoutSession(w, row) {
404
404
  currency: price.currency,
405
405
  provider: "fake",
406
406
  locale: row.locale,
407
- returnUrl: row.returnUrl,
408
- successUrl: row.successUrl ?? row.returnUrl,
407
+ successUrl: row.successUrl,
409
408
  cancelUrl: row.cancelUrl,
410
409
  consents: row.consents,
411
410
  actorRef: row.actorRef,
@@ -630,16 +629,6 @@ function saveProfile(row, profile) {
630
629
  row.profile = saved;
631
630
  return saved;
632
631
  }
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
632
  function accountDetail(w, ref, include) {
644
633
  const row = w.accounts.get(ref);
645
634
  if (!row) return null;
@@ -668,11 +657,8 @@ function accountDetail(w, ref, include) {
668
657
  if (want.has("invoices")) detail.invoices = newest(w.invoices.filter((i) => i.accountRef === ref)).map(toInvoice);
669
658
  return detail;
670
659
  }
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
660
  /** Hosted checkout sayfası (servisin `hostedCheckoutUrl`); sahtede `hosted.ts` sunar. */
674
661
  const hostedPageUrl = (baseUrl, id, pageKey) => `${baseUrl}/public/v1/checkout/${id}?k=${pageKey}`;
675
- const FAKE_PROVIDER_TOKEN_TTL_MS = 18e5;
676
662
  const PAGE_KEY_BYTES = 32;
677
663
  /** Servis: `appUrl`'in origin'i + `extraReturnOrigins` (sahte, ayarlanmadıysa her adresi kabul eder). */
678
664
  function returnUrlAllowed(w, url) {
@@ -687,56 +673,12 @@ function randomPageKey() {
687
673
  const bytes = globalThis.crypto.getRandomValues(new Uint8Array(PAGE_KEY_BYTES));
688
674
  return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
689
675
  }
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
676
  /**
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`).
677
+ * `createHostedCheckoutSession` (service): no provider call; validation order and codes match the
678
+ * service (account, return origins, live subscription, sellable price). A missing account is
679
+ * opened with `displayName`. `url` is the fake steward's hosted page (`hosted.ts`).
738
680
  */
739
- function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
681
+ function createCheckoutSession(w, baseUrl, ref, input, actorRef) {
740
682
  const existing = w.accounts.get(ref);
741
683
  if (!existing && input.displayName === void 0) return fail("account_not_found");
742
684
  const { settings } = w;
@@ -757,7 +699,6 @@ function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
757
699
  priceId: price.id,
758
700
  status: "open",
759
701
  locale: input.locale ?? settings.defaultLocale,
760
- returnUrl: input.successUrl,
761
702
  successUrl: input.successUrl,
762
703
  cancelUrl: input.cancelUrl ?? null,
763
704
  consentDocs: (input.consents ?? []).map(({ document, version, url }) => ({
@@ -773,8 +714,7 @@ function createHostedCheckoutSession(w, baseUrl, ref, input, actorRef) {
773
714
  subscriptionId: null,
774
715
  expiresAt: now + settings.checkoutTtlMinutes * 6e4,
775
716
  createdAt: now,
776
- completedAt: null,
777
- providerToken: null
717
+ completedAt: null
778
718
  };
779
719
  w.sessions.push(session);
780
720
  const { state } = commitAccountChange(w, ref, "checkout.opened");
@@ -831,11 +771,10 @@ function hostedProfile(row, prefill) {
831
771
  * Hosted oturum (F2b): sahte sayfada `details` adımı yok. Ödeme sonucundan önce servisteki
832
772
  * gönderimin izi yazılır: hesabın profili yoksa ön doldurmadan sahte profil (varsa aynen
833
773
  * 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;
774
+ * Hesap bu arada abone olduysa servis `details`'te oturumu kapatır;
835
775
  * çağıranlar o durumda bunu çağırmaz.
836
776
  */
837
777
  function submitHostedDetails(w, session) {
838
- if (session.successUrl === null) return;
839
778
  const row = account(w, session.accountRef);
840
779
  if (!row.profile) saveProfile(row, hostedProfile(row, session.prefill));
841
780
  const acceptedAt = iso(w.now());
@@ -1239,8 +1178,9 @@ function resyncAccount(w, ref) {
1239
1178
  }
1240
1179
  /**
1241
1180
  * `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:
1181
+ * silinmiş 410, canlı abonelik) ve hiçbiri tutmazsa değişiklik yok. The service's
1182
+ * `checkout_in_progress` (open provider form) never happens here: the fake hosted page has no
1183
+ * provider form, so open sessions are formless and deletion closes them. Sonra anonimleştirme:
1244
1184
  * ad/metadata/profil, oturumların ön doldurması/anahtarı ve onay izinin `ipHash`/`userAgent`'ı,
1245
1185
  * portal oturumları, aktif grant'ler; eski event yüklerinde `displayName`. Faturalar kalır.
1246
1186
  */
@@ -1250,8 +1190,6 @@ function deleteAccount(w, ref, actorRef) {
1250
1190
  if (row.deletedAt !== void 0) return fail("account_deleted");
1251
1191
  const live = liveSubscription(w, ref);
1252
1192
  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
1193
  const now = w.now();
1256
1194
  for (const session of w.sessions.filter((s) => s.accountRef === ref)) {
1257
1195
  if (session.status === "open") Object.assign(session, {
@@ -1501,33 +1439,10 @@ function portalInvoiceDocument(w, session, invoiceId) {
1501
1439
  filename: `${safe === "" ? "invoice" : safe}.pdf`
1502
1440
  };
1503
1441
  }
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
- }
1442
+ /** Requeues the event's delivery (dead or delivered included); `false` if there is no such event. */
1528
1443
  function redeliverEvent(w, id) {
1529
1444
  const event = w.events.find((e) => e.id === id);
1530
- if (!event) return null;
1445
+ if (!event) return false;
1531
1446
  if (event.delivery) Object.assign(event.delivery, {
1532
1447
  status: "pending",
1533
1448
  attempts: 0,
@@ -1535,7 +1450,7 @@ function redeliverEvent(w, id) {
1535
1450
  lastError: null,
1536
1451
  deliveredAt: null
1537
1452
  });
1538
- return toRecord(w, event);
1453
+ return true;
1539
1454
  }
1540
1455
  /**
1541
1456
  * Tahsilat sonucu. Dunning'deki abonelikte AYNI başarısız siparişin yeniden
@@ -1713,17 +1628,16 @@ function expireScheduledCancellations(w) {
1713
1628
  }
1714
1629
  }
1715
1630
  /**
1716
- * Süresi dolan açık oturum: sahte sağlayıcıda ödenmemiş form (`fake:not_completed`).
1631
+ * Expired open session (formless: the fake hosted page has no provider form, so `expired`).
1717
1632
  * Servis gibi kapanan oturumun `expiresAt`'i job kirasının sonu (o an + 5 dk) kalır.
1718
1633
  */
1719
1634
  function expireCheckoutSessions(w) {
1720
1635
  const now = w.now();
1721
1636
  for (const session of w.sessions) {
1722
1637
  if (session.status !== "open" || session.expiresAt > now) continue;
1723
- const failureReason = session.providerToken === null ? "expired" : "expired:fake:not_completed";
1724
1638
  Object.assign(session, {
1725
1639
  status: "expired",
1726
- failureReason,
1640
+ failureReason: "expired",
1727
1641
  completedAt: now,
1728
1642
  expiresAt: now + 3e5,
1729
1643
  ...CLOSED_SESSION_CLEANUP
@@ -2080,7 +1994,6 @@ const MUTATING = /* @__PURE__ */ new Set([
2080
1994
  "DELETE"
2081
1995
  ]);
2082
1996
  const KEY_RE = /^[A-Za-z0-9_.:-]{8,200}$/;
2083
- const EVENT_ID = /^evt_[0-9a-f-]{36}$/;
2084
1997
  const json = (body, status = 200, headers = {}) => ({
2085
1998
  status,
2086
1999
  body: JSON.stringify(body),
@@ -2206,23 +2119,6 @@ function routes(w, config) {
2206
2119
  if (notModified(ctx.headers.get("if-none-match"), etag)) return empty(304, { etag });
2207
2120
  return json(state, 200, { etag });
2208
2121
  }),
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
2122
  route("PUT", "accounts/:ref/plan", (ctx) => {
2227
2123
  const accountRef = ref(ctx);
2228
2124
  if (!accountRef) return ctx.error("invalid_account_ref");
@@ -2242,7 +2138,7 @@ function routes(w, config) {
2242
2138
  if (!accountRef) return ctx.error("invalid_account_ref");
2243
2139
  const parsed = parseBody(ctx, CheckoutSessionCreateInputSchema);
2244
2140
  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);
2141
+ const result = createCheckoutSession(w, config.baseUrl, accountRef, parsed.data, ctx.actorRef);
2246
2142
  return result.ok ? json(result.value, 201) : failure(ctx, result);
2247
2143
  }),
2248
2144
  route("POST", "accounts/:ref/portal-sessions", (ctx) => {
@@ -2314,25 +2210,6 @@ function routes(w, config) {
2314
2210
  "x-content-sha256": sha256
2315
2211
  }
2316
2212
  };
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
2213
  })
2337
2214
  ];
2338
2215
  }
@@ -3679,7 +3556,6 @@ function createFakeSteward(options = {}) {
3679
3556
  },
3680
3557
  async checkoutCanceled(sessionId) {
3681
3558
  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
3559
  cancelCheckout(w, row);
3684
3560
  return checkoutSessionResponse(w, sessionId);
3685
3561
  },
@@ -3744,6 +3620,9 @@ function createFakeSteward(options = {}) {
3744
3620
  out.push(delivery);
3745
3621
  }
3746
3622
  return out;
3623
+ },
3624
+ redeliver(eventId) {
3625
+ if (!redeliverEvent(w, eventId)) throw new Error(`simulate.redeliver: unknown event ${eventId}`);
3747
3626
  }
3748
3627
  },
3749
3628
  connect(next) {
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.2.0",
4
4
  "description": "Steward SDK: contract schemas, typed API client and webhook signing.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.9.0",
58
58
  "vitest": "^3.0.0",
59
59
  "workerd": "^1.20260916.1",
60
- "@z9cloud/steward-contract": "0.2.0"
60
+ "@steward/contract": "0.2.0"
61
61
  },
62
62
  "scripts": {
63
63
  "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 };