@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/README.md +27 -33
- package/dist/_chunks/errors.js +1 -1
- package/dist/_chunks/events.d.ts +15 -1
- package/dist/_chunks/events.js +172 -4
- package/dist/_chunks/index.d.ts +357 -3631
- package/dist/_chunks/src.js +85 -157
- package/dist/_chunks/steward.d.ts +5 -2404
- package/dist/_chunks/steward.js +12 -26
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +3 -3
- package/dist/index.d.ts +4 -66
- package/dist/index.js +4 -267
- package/dist/server.d.ts +6 -6
- package/dist/server.js +13 -2
- package/dist/testing.d.ts +60 -33
- package/dist/testing.js +70 -145
- package/package.json +4 -5
- package/dist/_chunks/locale.js +0 -17
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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` (
|
|
736
|
-
*
|
|
737
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
1243
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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 =
|
|
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.
|
|
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": "^
|
|
57
|
+
"vitest": "^5.0.1",
|
|
59
58
|
"workerd": "^1.20260916.1",
|
|
60
|
-
"@
|
|
59
|
+
"@steward/contract": "0.2.0"
|
|
61
60
|
},
|
|
62
61
|
"scripts": {
|
|
63
62
|
"build": "tsdown && node scripts/check-dist.mjs",
|
package/dist/_chunks/locale.js
DELETED
|
@@ -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 };
|