@stewardhq/sdk 0.1.0-rc.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 +437 -0
- package/dist/_chunks/errors.js +73 -0
- package/dist/_chunks/events.d.ts +537 -0
- package/dist/_chunks/events.js +350 -0
- package/dist/_chunks/index.d.ts +6005 -0
- package/dist/_chunks/locale.d.ts +17 -0
- package/dist/_chunks/locale.js +17 -0
- package/dist/_chunks/src.js +1202 -0
- package/dist/_chunks/steward.d.ts +2588 -0
- package/dist/_chunks/steward.js +578 -0
- package/dist/_chunks/validators.d.ts +23 -0
- package/dist/_chunks/validators.js +106 -0
- package/dist/_chunks/webhook-core.d.ts +32 -0
- package/dist/_chunks/webhook-core.js +64 -0
- package/dist/_chunks/webhook.d.ts +22 -0
- package/dist/_chunks/webhook.js +53 -0
- package/dist/contract.d.ts +4 -0
- package/dist/contract.js +4 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +784 -0
- package/dist/server.d.ts +236 -0
- package/dist/server.js +384 -0
- package/dist/testing/fixtures/events/LOCK.json +27 -0
- package/dist/testing/fixtures/events/account.deleted.json +36 -0
- package/dist/testing/fixtures/events/account.state_changed.json +53 -0
- package/dist/testing/fixtures/events/account.updated.json +37 -0
- package/dist/testing/fixtures/events/checkout.completed.json +36 -0
- package/dist/testing/fixtures/events/checkout.expired.json +26 -0
- package/dist/testing/fixtures/events/checkout.failed.json +27 -0
- package/dist/testing/fixtures/events/invoice.created.json +37 -0
- package/dist/testing/fixtures/events/invoice.issued.json +38 -0
- package/dist/testing/fixtures/events/invoice.voided.json +39 -0
- package/dist/testing/fixtures/events/subscription.activated.json +40 -0
- package/dist/testing/fixtures/events/subscription.cancel_scheduled.json +38 -0
- package/dist/testing/fixtures/events/subscription.canceled.json +29 -0
- package/dist/testing/fixtures/events/subscription.expired.json +27 -0
- package/dist/testing/fixtures/events/subscription.payment_failed.json +38 -0
- package/dist/testing/fixtures/events/subscription.reactivated.json +36 -0
- package/dist/testing/fixtures/events/subscription.renewed.json +39 -0
- package/dist/testing/fixtures/events/subscription.suspended.json +36 -0
- package/dist/testing/fixtures/events/subscription.terminated.json +28 -0
- package/dist/testing.d.ts +629 -0
- package/dist/testing.js +3759 -0
- package/dist/webhook/web.d.ts +17 -0
- package/dist/webhook/web.js +66 -0
- package/dist/webhook.d.ts +3 -0
- package/dist/webhook.js +3 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import { A as CodeSchema, B as IntervalSchema, C as BillingProfileSchema, D as API_VERSION, E as SubscriptionSummarySchema, F as EntitlementsSchema, I as ErrorCategorySchema, L as ErrorResponseSchema, M as DateTimeSchema, N as ERROR_CATEGORIES, O as ActorRefSchema, P as EntitlementValueSchema, R as ExternalRefSchema, S as BillingProfileKindSchema, T as SubscriptionStatusSchema, V as MetadataSchema, _ as AccountScheduledChangeSchema, a as EVENT_TYPES, b as AccountSubscriptionStateSchema, c as PING_EVENT_TYPE, d as ACCOUNT_INCLUDES, f as AccessStateSchema, g as AccountProfileStateSchema, h as AccountOpenCheckoutSchema, i as EVENT_DETAIL_SCHEMAS, j as CurrencySchema, k as AmountMinorSchema, l as PingSchema, m as AccountDunningStateSchema, n as ApiVersionSchema, o as EventIdSchema, p as AccountDeleteResponseSchema, r as BillingEventSchema, s as LEGACY_EVENT_TYPES, t as AnyBillingEventSchema, u as isEventType, v as AccountSchema, w as EntitlementSnapshotSchema, x as AccountUpsertInputSchema, y as AccountStateSchema, z as IdSchema } from "./_chunks/events.js";
|
|
2
|
+
import { $ as HEADER_SDK, $t as FeatureTypeSchema, A as webhookEndpointUrlProblem, At as MAX_CHECKOUT_CONSENT_DOCS, B as AccountDetailSchema, Bt as ReturnOriginSchema, C as WebhookEndpointListSchema, Ct as CheckoutConsentInputSchema, D as WebhookLastDeliverySchema, Dt as CheckoutSessionSchema, E as WebhookEndpointStatusSchema, Et as CheckoutSessionCreatedSchema, F as PlanSetWarningSchema, Ft as BrandingSchema, G as DeliverySchema, Gt as applySettingsPatch, H as BillingProfileResponseSchema, Ht as SettingsLocaleSchema, I as ResyncResultSchema, It as DEFAULT_CHECKOUT_TTL_MINUTES, J as GrantCreateResponseSchema, Jt as AccountsRefreshResultSchema, K as EventListSchema, Kt as isReturnUrlAllowed, L as ERROR_CODES, Lt as DEFAULT_PORTAL_TTL_MINUTES, M as PlanSetInputSchema, Mt as checkoutRedirectUrl, N as PlanSetModeSchema, Nt as isLegacyCheckoutInput, O as WebhookSecretSchema, Ot as CheckoutStatusSchema, P as PlanSetResponseSchema, Pt as BrandingPatchSchema, Q as HEADER_REQUEST_ID, Qt as FeatureDefSchema, R as errorCodeSpec, Rt as ProductSettingsPatchSchema, S as WebhookEndpointCreateInputSchema, St as CHECKOUT_ERRORS, T as WebhookEndpointSchema, Tt as CheckoutSessionCreateInputSchema, U as CancelSubscriptionResponseSchema, Ut as TtlMinutesSchema, V as AccountUpsertResponseSchema, Vt as SETTINGS_LOCALES, W as CheckoutSessionResponseSchema, Wt as WebUrlSchema, X as HEADER_API_VERSION, Xt as CatalogSyncInputSchema, Y as HEADER_ACTOR_REF, Yt as CatalogSchema, Z as HEADER_IDEMPOTENCY_KEY, Zt as CatalogSyncResultSchema, _ as hostedReadinessOf, _t as StatsSchema, a as DOCTOR_SEVERITIES, at as InvoiceSchema, b as WEBHOOK_ENDPOINT_STATUSES, bt as PortalSessionCreateInputSchema, c as DoctorHostedSchema, ct as MANUAL_INVOICE_MAX_AMOUNT_MINOR, d as DoctorSettingsSchema, dt as manualInvoiceTotals, en as PlanDefSchema, et as InvoiceIssueResponseSchema, f as DoctorSeveritySchema, ft as splitInclusiveTax, g as endpointDiagnosisOf, gt as PaymentSchema, h as buildDoctorReport, ht as GrantSchema, i as DOCTOR_REQUIRED_LEGAL_LINKS, it as InvoiceLineSchema, j as PLAN_SET_MODES, jt as ProviderPayloadSchema, k as generateWebhookSecret, kt as ConsentSchema, l as DoctorProviderSchema, lt as ManualInvoiceInputSchema, m as DoctorWarningSchema, mt as GrantCreateInputSchema, n as DOCTOR_PROVIDER_ENVIRONMENTS, nn as PriceSchema, nt as ManualInvoiceResponseSchema, o as DoctorCatalogSchema, ot as InvoiceStatusSchema, p as DoctorUnsellablePriceSchema, pt as CancelSubscriptionInputSchema, q as EventRecordSchema, qt as returnOriginsOf, r as DOCTOR_REQUIRED_BRANDING, rt as InvoiceIssueInputSchema, s as DoctorEndpointSchema, st as InvoiceVoidInputSchema, t as DOCTOR_PENDING_BACKLOG_MS, tn as PriceDefSchema, tt as InvoiceVoidResponseSchema, u as DoctorReportSchema, ut as ManualInvoiceLineInputSchema, v as DEFAULT_ENDPOINT_EVENT_TYPES, vt as SubscriptionSchema, w as WebhookEndpointRotateInputSchema, wt as CheckoutPrefillSchema, x as WEBHOOK_SECRET_PREFIX, xt as PortalSessionSchema, y as EventTypeSchema, yt as PORTAL_SESSION_ERRORS, z as isKnownErrorCode, zt as ProductSettingsSchema } from "./_chunks/src.js";
|
|
3
|
+
import { a as isGrantActive, i as LIVE_SUBSCRIPTION_STATUSES, n as isValidTckn, o as isLiveSubscriptionStatus, r as isValidVkn, s as resolveEntitlements, t as isE164 } from "./_chunks/validators.js";
|
|
4
|
+
import { n as createBillingClient, r as parseRetryAfter, t as createSteward } from "./_chunks/steward.js";
|
|
5
|
+
import { a as StewardNetworkError, i as StewardError, n as StewardConfigError, r as StewardContractError, t as StewardApiError } from "./_chunks/errors.js";
|
|
6
|
+
import { n as localeOf } from "./_chunks/locale.js";
|
|
7
|
+
//#region src/catalog-cache.ts
|
|
8
|
+
function createCatalogCache(steward, options = {}) {
|
|
9
|
+
const ttlMs = options.ttlMs ?? 6e4;
|
|
10
|
+
if (!Number.isFinite(ttlMs) || ttlMs < 0) throw new StewardConfigError({ message: `createCatalogCache: ttlMs negatif olmayan sayı olmalı: ${ttlMs}` });
|
|
11
|
+
const logger = options.logger ?? console;
|
|
12
|
+
let entry = null;
|
|
13
|
+
let inflight = null;
|
|
14
|
+
async function refresh() {
|
|
15
|
+
const previous = entry;
|
|
16
|
+
try {
|
|
17
|
+
const read = await steward.catalog.read(previous?.etag ? { etag: previous.etag } : {});
|
|
18
|
+
let next;
|
|
19
|
+
if (!read.notModified) next = {
|
|
20
|
+
catalog: read.catalog,
|
|
21
|
+
etag: read.etag,
|
|
22
|
+
fetchedAt: Date.now()
|
|
23
|
+
};
|
|
24
|
+
else if (previous !== null) next = {
|
|
25
|
+
...previous,
|
|
26
|
+
etag: read.etag,
|
|
27
|
+
fetchedAt: Date.now()
|
|
28
|
+
};
|
|
29
|
+
else throw new StewardContractError({ httpStatus: 304 });
|
|
30
|
+
entry = next;
|
|
31
|
+
return next.catalog;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (previous === null) throw error;
|
|
34
|
+
const { code, requestId } = typeof error === "object" && error !== null ? error : {};
|
|
35
|
+
logger.warn("steward katalogu tazelenemedi; bayat değer kullanılıyor", {
|
|
36
|
+
code: typeof code === "string" ? code : null,
|
|
37
|
+
requestId: typeof requestId === "string" ? requestId : null,
|
|
38
|
+
ageMs: Date.now() - previous.fetchedAt
|
|
39
|
+
});
|
|
40
|
+
return previous.catalog;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
get() {
|
|
45
|
+
if (entry !== null && Date.now() - entry.fetchedAt < ttlMs) return Promise.resolve(entry.catalog);
|
|
46
|
+
inflight ??= refresh().finally(() => {
|
|
47
|
+
inflight = null;
|
|
48
|
+
});
|
|
49
|
+
return inflight;
|
|
50
|
+
},
|
|
51
|
+
invalidate() {
|
|
52
|
+
if (entry !== null) entry = {
|
|
53
|
+
...entry,
|
|
54
|
+
fetchedAt: Number.NEGATIVE_INFINITY
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/state-cache.ts
|
|
61
|
+
const MANY_CONCURRENCY = 8;
|
|
62
|
+
const FAILURE_BACKOFF_MS = 5e3;
|
|
63
|
+
function fieldsOf$1(error) {
|
|
64
|
+
const { code, httpStatus, requestId } = typeof error === "object" && error !== null ? error : {};
|
|
65
|
+
return {
|
|
66
|
+
code: typeof code === "string" ? code : null,
|
|
67
|
+
httpStatus: typeof httpStatus === "number" ? httpStatus : null,
|
|
68
|
+
requestId: typeof requestId === "string" ? requestId : null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function isOutage({ code, httpStatus }) {
|
|
72
|
+
if (httpStatus === 0) return code === "network_error" || code === "timeout";
|
|
73
|
+
return httpStatus !== null && (httpStatus >= 500 || httpStatus === 429);
|
|
74
|
+
}
|
|
75
|
+
const isNotFound = ({ code, httpStatus }) => httpStatus === 404 && code === "account_not_found";
|
|
76
|
+
function stateCache(steward, options = {}) {
|
|
77
|
+
const ttlMs = options.ttlMs ?? 3e4;
|
|
78
|
+
const staleIfErrorMs = options.staleIfErrorMs ?? 6e5;
|
|
79
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
80
|
+
const problems = [];
|
|
81
|
+
if (!(Number.isFinite(ttlMs) && ttlMs >= 0)) problems.push(`ttlMs negatif olmayan sayı olmalı: ${ttlMs}`);
|
|
82
|
+
if (!(Number.isFinite(staleIfErrorMs) && staleIfErrorMs >= 0)) problems.push(`staleIfErrorMs negatif olmayan sayı olmalı: ${staleIfErrorMs}`);
|
|
83
|
+
if (!(Number.isInteger(maxEntries) && maxEntries >= 1)) problems.push(`maxEntries pozitif tam sayı olmalı: ${maxEntries}`);
|
|
84
|
+
const missing = options.missing ?? "throw";
|
|
85
|
+
if (missing !== "throw" && missing !== "fallback") problems.push(`missing "throw" ya da "fallback" olmalı: ${String(missing)}`);
|
|
86
|
+
else if (missing === "fallback" && typeof options.fallback !== "function") problems.push("missing: \"fallback\" için fallback gerekli");
|
|
87
|
+
if (problems.length > 0) throw new StewardConfigError({ message: `stateCache: ${problems.join("; ")}` });
|
|
88
|
+
const { catalog, fallback, onFallback } = options;
|
|
89
|
+
const logger = options.logger ?? console;
|
|
90
|
+
const now = options.now ?? Date.now;
|
|
91
|
+
const backoffMs = Math.min(ttlMs, FAILURE_BACKOFF_MS);
|
|
92
|
+
const entries = /* @__PURE__ */ new Map();
|
|
93
|
+
const flights = /* @__PURE__ */ new Map();
|
|
94
|
+
let seq = 0;
|
|
95
|
+
function touch(ref) {
|
|
96
|
+
const entry = entries.get(ref);
|
|
97
|
+
if (entry) {
|
|
98
|
+
entries.delete(ref);
|
|
99
|
+
entries.set(ref, entry);
|
|
100
|
+
}
|
|
101
|
+
return entry;
|
|
102
|
+
}
|
|
103
|
+
function put(ref, entry) {
|
|
104
|
+
entries.delete(ref);
|
|
105
|
+
entries.set(ref, entry);
|
|
106
|
+
for (const oldest of entries.keys()) {
|
|
107
|
+
if (entries.size <= maxEntries) break;
|
|
108
|
+
entries.delete(oldest);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function confirmed(state, etag, at, expired = false) {
|
|
112
|
+
return {
|
|
113
|
+
state,
|
|
114
|
+
etag,
|
|
115
|
+
fetchedAt: at,
|
|
116
|
+
expiresAt: expired ? Number.NEGATIVE_INFINITY : at + ttlMs,
|
|
117
|
+
seq: ++seq,
|
|
118
|
+
failure: null
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Kesintide: bayat değer → fallback → asıl hata. WARN her başarısız istekte ve bayattan fallback'e geçişte bir kez. */
|
|
122
|
+
async function degrade(ref, failure) {
|
|
123
|
+
const { error } = failure;
|
|
124
|
+
const { code, requestId } = fieldsOf$1(error);
|
|
125
|
+
const entry = entries.get(ref);
|
|
126
|
+
const t = now();
|
|
127
|
+
if (entry?.state && t < entry.fetchedAt + ttlMs + staleIfErrorMs) {
|
|
128
|
+
if (failure.warned !== "stale") logger.warn("steward hesap durumu okunamadı; bayat değer kullanılıyor", {
|
|
129
|
+
ref,
|
|
130
|
+
code,
|
|
131
|
+
requestId,
|
|
132
|
+
ageMs: t - entry.fetchedAt
|
|
133
|
+
});
|
|
134
|
+
failure.warned = "stale";
|
|
135
|
+
return entry.state;
|
|
136
|
+
}
|
|
137
|
+
if (fallback) {
|
|
138
|
+
let value;
|
|
139
|
+
try {
|
|
140
|
+
value = await fallback(ref);
|
|
141
|
+
} catch (fallbackError) {
|
|
142
|
+
logger.warn("stateCache fallback fırlattı; steward hatası iletiliyor", {
|
|
143
|
+
ref,
|
|
144
|
+
code,
|
|
145
|
+
error: String(fallbackError)
|
|
146
|
+
});
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
if (value) {
|
|
150
|
+
if (failure.warned !== "fallback") logger.warn("steward hesap durumu okunamadı, bayat değer yok; fallback kullanılıyor", {
|
|
151
|
+
ref,
|
|
152
|
+
code,
|
|
153
|
+
requestId
|
|
154
|
+
});
|
|
155
|
+
failure.warned = "fallback";
|
|
156
|
+
try {
|
|
157
|
+
onFallback?.({
|
|
158
|
+
ref,
|
|
159
|
+
reason: code ?? "unknown"
|
|
160
|
+
});
|
|
161
|
+
} catch (hookError) {
|
|
162
|
+
logger.warn("stateCache onFallback fırlattı", {
|
|
163
|
+
ref,
|
|
164
|
+
error: String(hookError)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
/** `missing: "fallback"` ile bilinmeyen hesap: bayat değer yok, WARN yok; fallback fırlatır ya da `null` dönerse 404. */
|
|
173
|
+
async function missingFallback(ref, failure) {
|
|
174
|
+
let value;
|
|
175
|
+
try {
|
|
176
|
+
value = await fallback(ref);
|
|
177
|
+
} catch (fallbackError) {
|
|
178
|
+
logger.warn("stateCache fallback fırlattı; steward hatası iletiliyor", {
|
|
179
|
+
ref,
|
|
180
|
+
code: "account_not_found",
|
|
181
|
+
error: String(fallbackError)
|
|
182
|
+
});
|
|
183
|
+
throw failure.error;
|
|
184
|
+
}
|
|
185
|
+
if (!value) throw failure.error;
|
|
186
|
+
try {
|
|
187
|
+
onFallback?.({
|
|
188
|
+
ref,
|
|
189
|
+
reason: "account_not_found"
|
|
190
|
+
});
|
|
191
|
+
} catch (hookError) {
|
|
192
|
+
logger.warn("stateCache onFallback fırlattı", {
|
|
193
|
+
ref,
|
|
194
|
+
error: String(hookError)
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
async function load(ref, started, flight) {
|
|
200
|
+
const known = entries.get(ref);
|
|
201
|
+
let read;
|
|
202
|
+
try {
|
|
203
|
+
read = await steward.accounts.readState(ref, known?.state && known.etag !== null ? { etag: known.etag } : {});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const fields = fieldsOf$1(error);
|
|
206
|
+
if (isNotFound(fields)) {
|
|
207
|
+
if (missing === "throw") {
|
|
208
|
+
entries.delete(ref);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
const failure = {
|
|
212
|
+
error,
|
|
213
|
+
until: now() + backoffMs,
|
|
214
|
+
warned: null,
|
|
215
|
+
missing: true
|
|
216
|
+
};
|
|
217
|
+
put(ref, {
|
|
218
|
+
state: null,
|
|
219
|
+
etag: null,
|
|
220
|
+
fetchedAt: Number.NEGATIVE_INFINITY,
|
|
221
|
+
expiresAt: Number.NEGATIVE_INFINITY,
|
|
222
|
+
seq: ++seq,
|
|
223
|
+
failure
|
|
224
|
+
});
|
|
225
|
+
return missingFallback(ref, failure);
|
|
226
|
+
}
|
|
227
|
+
if (!isOutage(fields)) throw error;
|
|
228
|
+
const current = entries.get(ref);
|
|
229
|
+
const failure = {
|
|
230
|
+
error,
|
|
231
|
+
until: now() + backoffMs,
|
|
232
|
+
warned: null
|
|
233
|
+
};
|
|
234
|
+
if (current) current.failure = failure;
|
|
235
|
+
else put(ref, {
|
|
236
|
+
state: null,
|
|
237
|
+
etag: null,
|
|
238
|
+
fetchedAt: Number.NEGATIVE_INFINITY,
|
|
239
|
+
expiresAt: Number.NEGATIVE_INFINITY,
|
|
240
|
+
seq: ++seq,
|
|
241
|
+
failure
|
|
242
|
+
});
|
|
243
|
+
return degrade(ref, failure);
|
|
244
|
+
}
|
|
245
|
+
let state;
|
|
246
|
+
if (!read.notModified) state = read.state;
|
|
247
|
+
else if (known?.state) state = known.state;
|
|
248
|
+
else throw new StewardContractError({ httpStatus: 304 });
|
|
249
|
+
const current = entries.get(ref);
|
|
250
|
+
if (current?.state && current.seq > started && current.state.version >= state.version) return current.state;
|
|
251
|
+
put(ref, confirmed(state, read.etag, now(), flight.invalidated));
|
|
252
|
+
return state;
|
|
253
|
+
}
|
|
254
|
+
function join(ref) {
|
|
255
|
+
const running = flights.get(ref);
|
|
256
|
+
if (running) return running.promise;
|
|
257
|
+
const control = { invalidated: false };
|
|
258
|
+
const promise = load(ref, seq, control).finally(() => {
|
|
259
|
+
if (flights.get(ref)?.promise === promise) flights.delete(ref);
|
|
260
|
+
});
|
|
261
|
+
flights.set(ref, Object.assign(control, { promise }));
|
|
262
|
+
return promise;
|
|
263
|
+
}
|
|
264
|
+
async function state(ref, opts = {}) {
|
|
265
|
+
const entry = touch(ref);
|
|
266
|
+
if (entry && !opts.fresh) {
|
|
267
|
+
const t = now();
|
|
268
|
+
if (entry.state && t < entry.expiresAt) return entry.state;
|
|
269
|
+
if (entry.failure && t < entry.failure.until) return entry.failure.missing ? missingFallback(ref, entry.failure) : degrade(ref, entry.failure);
|
|
270
|
+
}
|
|
271
|
+
return join(ref);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
state,
|
|
275
|
+
async entitlements(ref, opts) {
|
|
276
|
+
const current = await state(ref, opts);
|
|
277
|
+
if (!catalog) return current.entitlements;
|
|
278
|
+
return catalog.resolve(current, { onFallback: (keys) => logger.warn("steward durumu katalogdaki anahtarları taşımıyor; plan değerleri kullanıldı", {
|
|
279
|
+
ref,
|
|
280
|
+
keys
|
|
281
|
+
}) });
|
|
282
|
+
},
|
|
283
|
+
async many(refs) {
|
|
284
|
+
const unique = [...new Set(refs)];
|
|
285
|
+
const results = /* @__PURE__ */ new Map();
|
|
286
|
+
const errors = [];
|
|
287
|
+
let next = 0;
|
|
288
|
+
const worker = async () => {
|
|
289
|
+
while (errors.length === 0 && next < unique.length) {
|
|
290
|
+
const ref = unique[next++];
|
|
291
|
+
try {
|
|
292
|
+
results.set(ref, await state(ref));
|
|
293
|
+
} catch (error) {
|
|
294
|
+
errors.push(error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
await Promise.all(Array.from({ length: Math.min(MANY_CONCURRENCY, unique.length) }, worker));
|
|
299
|
+
if (errors.length > 0) throw errors[0];
|
|
300
|
+
return new Map(unique.map((ref) => [ref, results.get(ref)]));
|
|
301
|
+
},
|
|
302
|
+
invalidate(ref) {
|
|
303
|
+
const flight = flights.get(ref);
|
|
304
|
+
if (flight) {
|
|
305
|
+
flight.invalidated = true;
|
|
306
|
+
flights.delete(ref);
|
|
307
|
+
}
|
|
308
|
+
const entry = entries.get(ref);
|
|
309
|
+
if (entry) Object.assign(entry, {
|
|
310
|
+
expiresAt: Number.NEGATIVE_INFINITY,
|
|
311
|
+
failure: null
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
prime(account) {
|
|
315
|
+
if (!account) return;
|
|
316
|
+
const current = entries.get(account.ref);
|
|
317
|
+
if (current?.state && account.version < current.state.version) return;
|
|
318
|
+
const etag = current?.state?.version === account.version ? current.etag : null;
|
|
319
|
+
put(account.ref, confirmed(account, etag, now()));
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/http-status.ts
|
|
325
|
+
const STATUS = {
|
|
326
|
+
already_subscribed: 409,
|
|
327
|
+
plan_not_sellable: 422,
|
|
328
|
+
account_not_found: 404,
|
|
329
|
+
provider_error: 502,
|
|
330
|
+
provider_not_configured: 503,
|
|
331
|
+
timeout: 503,
|
|
332
|
+
network_error: 503,
|
|
333
|
+
rate_limited: 503,
|
|
334
|
+
return_url_not_allowed: 500
|
|
335
|
+
};
|
|
336
|
+
const BY_CATEGORY = {
|
|
337
|
+
not_found: 404,
|
|
338
|
+
conflict: 409,
|
|
339
|
+
provider: 502,
|
|
340
|
+
unavailable: 503
|
|
341
|
+
};
|
|
342
|
+
const isCategory = (value) => typeof value === "string" && ERROR_CATEGORIES.includes(value);
|
|
343
|
+
function fieldsOf(err) {
|
|
344
|
+
if (typeof err !== "object" || err === null) return null;
|
|
345
|
+
const { code, category, retryable, requestId, retryAfterMs } = err;
|
|
346
|
+
if (typeof code !== "string") return null;
|
|
347
|
+
const spec = errorCodeSpec(code);
|
|
348
|
+
const resolvedCategory = isCategory(category) ? category : spec?.category;
|
|
349
|
+
const resolvedRetryable = typeof retryable === "boolean" ? retryable : spec?.retryable;
|
|
350
|
+
return {
|
|
351
|
+
code,
|
|
352
|
+
...resolvedCategory !== void 0 ? { category: resolvedCategory } : {},
|
|
353
|
+
...resolvedRetryable !== void 0 ? { retryable: resolvedRetryable } : {},
|
|
354
|
+
...typeof requestId === "string" ? { requestId } : {},
|
|
355
|
+
...typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? { retryAfterMs } : {}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const retryAfter = (fields) => fields.retryAfterMs === void 0 ? {} : { "Retry-After": String(Math.max(1, Math.ceil(fields.retryAfterMs / 1e3))) };
|
|
359
|
+
function httpStatusFor(err, options = {}) {
|
|
360
|
+
const logger = options.logger ?? console;
|
|
361
|
+
const fields = fieldsOf(err);
|
|
362
|
+
if (!fields) return {
|
|
363
|
+
status: 500,
|
|
364
|
+
headers: {}
|
|
365
|
+
};
|
|
366
|
+
const context = {
|
|
367
|
+
code: fields.code,
|
|
368
|
+
requestId: fields.requestId ?? null
|
|
369
|
+
};
|
|
370
|
+
if (Object.hasOwn(STATUS, fields.code)) {
|
|
371
|
+
const status = STATUS[fields.code];
|
|
372
|
+
switch (fields.code) {
|
|
373
|
+
case "rate_limited":
|
|
374
|
+
logger.warn("steward rate_limited: ürünün steward bütçesi aşıldı", {
|
|
375
|
+
...context,
|
|
376
|
+
retryAfterMs: fields.retryAfterMs ?? null
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
status,
|
|
380
|
+
headers: retryAfter(fields)
|
|
381
|
+
};
|
|
382
|
+
case "return_url_not_allowed":
|
|
383
|
+
logger.error("ALARM steward return_url_not_allowed: steward ürün ayarlarında uygulamanın origin'i yok", context);
|
|
384
|
+
return {
|
|
385
|
+
status,
|
|
386
|
+
headers: {}
|
|
387
|
+
};
|
|
388
|
+
default: return {
|
|
389
|
+
status,
|
|
390
|
+
headers: {}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const byCategory = fields.category === void 0 ? void 0 : BY_CATEGORY[fields.category];
|
|
395
|
+
if (byCategory !== void 0) return {
|
|
396
|
+
status: byCategory,
|
|
397
|
+
headers: byCategory === 503 ? retryAfter(fields) : {}
|
|
398
|
+
};
|
|
399
|
+
if (fields.category === void 0 && fields.retryable === true) return {
|
|
400
|
+
status: 503,
|
|
401
|
+
headers: retryAfter(fields)
|
|
402
|
+
};
|
|
403
|
+
logger.error(`steward hatası eşlenmedi (${fields.code}) → 500`, {
|
|
404
|
+
...context,
|
|
405
|
+
category: fields.category ?? null
|
|
406
|
+
});
|
|
407
|
+
return {
|
|
408
|
+
status: 500,
|
|
409
|
+
headers: {}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/messages.ts
|
|
414
|
+
/** SDK'nın kendisinin ürettiği kodlar (yanıt yok ya da istek hiç gönderilmedi). */
|
|
415
|
+
const CLIENT_ERROR_CODES = [
|
|
416
|
+
"network_error",
|
|
417
|
+
"timeout",
|
|
418
|
+
"invalid_response",
|
|
419
|
+
"config_error"
|
|
420
|
+
];
|
|
421
|
+
const STEWARD_ERROR_CODES = Object.freeze([...Object.keys(ERROR_CODES), ...CLIENT_ERROR_CODES]);
|
|
422
|
+
const TR_CONFIG = "Ödeme sistemi yapılandırmasında bir sorun var. Lütfen destekle iletişime geçin.";
|
|
423
|
+
const TR_RETRY = "İstek işlenemedi. Lütfen tekrar deneyin.";
|
|
424
|
+
const TR_LATER = "Ödeme sistemi şu anda kullanılamıyor. Lütfen biraz sonra tekrar deneyin.";
|
|
425
|
+
const EN_CONFIG = "There is a problem with the billing configuration. Please contact support.";
|
|
426
|
+
const EN_RETRY = "The request could not be processed. Please try again.";
|
|
427
|
+
const errorMessages = {
|
|
428
|
+
tr: {
|
|
429
|
+
unauthorized: TR_CONFIG,
|
|
430
|
+
forbidden: TR_CONFIG,
|
|
431
|
+
invalid_actor_ref: TR_RETRY,
|
|
432
|
+
actor_ref_required: TR_RETRY,
|
|
433
|
+
idempotency_key_required: TR_RETRY,
|
|
434
|
+
invalid_idempotency_key: TR_RETRY,
|
|
435
|
+
idempotency_key_reused: "Bu istek daha önce farklı bilgilerle gönderildi. Sayfayı yenileyip tekrar deneyin.",
|
|
436
|
+
request_in_progress: "Aynı işlem şu anda sürüyor. Birkaç saniye sonra tekrar deneyin.",
|
|
437
|
+
invalid_json: TR_RETRY,
|
|
438
|
+
invalid_request: "Gönderilen bilgiler geçersiz. Lütfen kontrol edip tekrar deneyin.",
|
|
439
|
+
invalid_account_ref: "Hesap bilgisi geçersiz.",
|
|
440
|
+
invalid_include: TR_RETRY,
|
|
441
|
+
invalid_cursor: TR_RETRY,
|
|
442
|
+
invalid_limit: TR_RETRY,
|
|
443
|
+
not_found: "Kayıt bulunamadı.",
|
|
444
|
+
account_not_found: "Faturalandırma hesabı bulunamadı.",
|
|
445
|
+
account_deleted: "Faturalandırma hesabı silinmiş.",
|
|
446
|
+
subscription_active: "Hesabın etkin bir aboneliği var; önce aboneliği iptal edin.",
|
|
447
|
+
checkout_in_progress: "Hesapta süren bir ödeme işlemi var. Lütfen daha sonra tekrar deneyin.",
|
|
448
|
+
checkout_session_not_found: "Ödeme oturumu bulunamadı.",
|
|
449
|
+
subscription_not_found: "Abonelik bulunamadı.",
|
|
450
|
+
invoice_not_found: "Fatura bulunamadı.",
|
|
451
|
+
document_not_found: "Fatura belgesi bulunamadı.",
|
|
452
|
+
grant_not_found: "Tanımlı plan hakkı bulunamadı.",
|
|
453
|
+
event_not_found: "Kayıt bulunamadı.",
|
|
454
|
+
webhook_endpoint_not_found: "Kayıt bulunamadı.",
|
|
455
|
+
plan_not_found: "Plan bulunamadı.",
|
|
456
|
+
catalog_not_configured: "Planlar henüz tanımlanmadı. Lütfen daha sonra tekrar deneyin.",
|
|
457
|
+
already_subscribed: "Bu hesabın zaten etkin bir aboneliği var.",
|
|
458
|
+
plan_not_sellable: "Bu plan satın alınamıyor.",
|
|
459
|
+
return_url_not_allowed: TR_CONFIG,
|
|
460
|
+
provider_not_configured: "Şu anda ödeme alınamıyor. Lütfen daha sonra tekrar deneyin.",
|
|
461
|
+
provider_error: "Ödeme sağlayıcısında bir sorun oluştu. Lütfen biraz sonra tekrar deneyin.",
|
|
462
|
+
already_canceled: "Abonelik zaten iptal edilmiş.",
|
|
463
|
+
subscription_plan_higher: "Hesabın etkin aboneliği bu plandan daha yüksek bir planda.",
|
|
464
|
+
override_type_mismatch: "Hak değeri, özelliğin türüyle uyuşmuyor.",
|
|
465
|
+
invalid_window: "Bitiş tarihi başlangıçtan sonra olmalı.",
|
|
466
|
+
plan_missing_feature: "Katalogdaki bir plan, özelliklerden birine değer vermiyor.",
|
|
467
|
+
already_issued: "Bu fatura zaten kesilmiş.",
|
|
468
|
+
number_taken: "Bu fatura numarası başka bir faturada kullanılıyor.",
|
|
469
|
+
invalid_document: "Fatura belgesi geçersiz: en fazla 5 MB PDF yükleyin.",
|
|
470
|
+
billing_profile_required: "Fatura için önce hesabın fatura bilgileri girilmeli.",
|
|
471
|
+
invoice_not_voidable: "Bu fatura zaten iptal edilmiş.",
|
|
472
|
+
invoice_has_payment: "Ödemesi alınmış fatura iptal edilemez; iade süreci gerekir.",
|
|
473
|
+
internal_error: "Ödeme sisteminde beklenmeyen bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
|
|
474
|
+
database_unavailable: TR_LATER,
|
|
475
|
+
unknown_provider: TR_CONFIG,
|
|
476
|
+
bad_signature: TR_CONFIG,
|
|
477
|
+
invalid_payload: TR_RETRY,
|
|
478
|
+
stale_event: TR_RETRY,
|
|
479
|
+
not_configured: "Şu anda ödeme alınamıyor. Lütfen daha sonra tekrar deneyin.",
|
|
480
|
+
unknown_subscription: "Abonelik bulunamadı.",
|
|
481
|
+
network_error: "Ödeme sistemine ulaşılamadı. Lütfen biraz sonra tekrar deneyin.",
|
|
482
|
+
timeout: "Ödeme sistemi zamanında yanıt vermedi. Lütfen biraz sonra tekrar deneyin.",
|
|
483
|
+
rate_limited: "Şu anda çok fazla istek var. Lütfen birkaç saniye sonra tekrar deneyin.",
|
|
484
|
+
invalid_response: "Ödeme sisteminden beklenmeyen bir yanıt alındı. Lütfen daha sonra tekrar deneyin.",
|
|
485
|
+
config_error: TR_CONFIG,
|
|
486
|
+
fallback: "Bir hata oluştu. Lütfen daha sonra tekrar deneyin."
|
|
487
|
+
},
|
|
488
|
+
en: {
|
|
489
|
+
unauthorized: EN_CONFIG,
|
|
490
|
+
forbidden: EN_CONFIG,
|
|
491
|
+
invalid_actor_ref: EN_RETRY,
|
|
492
|
+
actor_ref_required: EN_RETRY,
|
|
493
|
+
idempotency_key_required: EN_RETRY,
|
|
494
|
+
invalid_idempotency_key: EN_RETRY,
|
|
495
|
+
idempotency_key_reused: "This request was already sent with different details. Refresh the page and try again.",
|
|
496
|
+
request_in_progress: "The same operation is already in progress. Try again in a few seconds.",
|
|
497
|
+
invalid_json: EN_RETRY,
|
|
498
|
+
invalid_request: "Some of the details are invalid. Please check them and try again.",
|
|
499
|
+
invalid_account_ref: "The account reference is invalid.",
|
|
500
|
+
invalid_include: EN_RETRY,
|
|
501
|
+
invalid_cursor: EN_RETRY,
|
|
502
|
+
invalid_limit: EN_RETRY,
|
|
503
|
+
not_found: "Not found.",
|
|
504
|
+
account_not_found: "Billing account not found.",
|
|
505
|
+
account_deleted: "The billing account has been deleted.",
|
|
506
|
+
subscription_active: "The account has an active subscription; cancel it first.",
|
|
507
|
+
checkout_in_progress: "A payment is in progress for this account. Please try again later.",
|
|
508
|
+
checkout_session_not_found: "Checkout session not found.",
|
|
509
|
+
subscription_not_found: "Subscription not found.",
|
|
510
|
+
invoice_not_found: "Invoice not found.",
|
|
511
|
+
document_not_found: "Invoice document not found.",
|
|
512
|
+
grant_not_found: "Plan grant not found.",
|
|
513
|
+
event_not_found: "Not found.",
|
|
514
|
+
webhook_endpoint_not_found: "Not found.",
|
|
515
|
+
plan_not_found: "Plan not found.",
|
|
516
|
+
catalog_not_configured: "Plans are not set up yet. Please try again later.",
|
|
517
|
+
already_subscribed: "This account already has an active subscription.",
|
|
518
|
+
plan_not_sellable: "This plan cannot be purchased.",
|
|
519
|
+
return_url_not_allowed: EN_CONFIG,
|
|
520
|
+
provider_not_configured: "Payments are unavailable right now. Please try again later.",
|
|
521
|
+
provider_error: "The payment provider ran into a problem. Please try again shortly.",
|
|
522
|
+
already_canceled: "The subscription is already canceled.",
|
|
523
|
+
subscription_plan_higher: "The account's active subscription is on a higher plan than this one.",
|
|
524
|
+
override_type_mismatch: "The entitlement value does not match the feature type.",
|
|
525
|
+
invalid_window: "The end date must be after the start date.",
|
|
526
|
+
plan_missing_feature: "A plan in the catalog is missing a value for one of the features.",
|
|
527
|
+
already_issued: "This invoice has already been issued.",
|
|
528
|
+
number_taken: "This invoice number is already used by another invoice.",
|
|
529
|
+
invalid_document: "Invalid invoice document: upload a PDF of at most 5 MB.",
|
|
530
|
+
billing_profile_required: "Enter the account's billing details before creating an invoice.",
|
|
531
|
+
invoice_not_voidable: "This invoice is already void.",
|
|
532
|
+
invoice_has_payment: "A paid invoice cannot be voided; it requires a refund.",
|
|
533
|
+
internal_error: "Billing ran into an unexpected error. Please try again later.",
|
|
534
|
+
database_unavailable: "Billing is temporarily unavailable. Please try again shortly.",
|
|
535
|
+
unknown_provider: EN_CONFIG,
|
|
536
|
+
bad_signature: EN_CONFIG,
|
|
537
|
+
invalid_payload: EN_RETRY,
|
|
538
|
+
stale_event: EN_RETRY,
|
|
539
|
+
not_configured: "Payments are unavailable right now. Please try again later.",
|
|
540
|
+
unknown_subscription: "Subscription not found.",
|
|
541
|
+
network_error: "Could not reach billing. Please try again shortly.",
|
|
542
|
+
timeout: "Billing did not respond in time. Please try again shortly.",
|
|
543
|
+
rate_limited: "Too many requests right now. Please try again in a few seconds.",
|
|
544
|
+
invalid_response: "Billing returned an unexpected response. Please try again later.",
|
|
545
|
+
config_error: EN_CONFIG,
|
|
546
|
+
fallback: "Something went wrong. Please try again later."
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
/** Kullanıcıya gösterilecek metin (`tr`, `tr-TR` → tr; diğer diller → en); tanınmayan kodda genel metin. */
|
|
550
|
+
function messageFor(code, locale) {
|
|
551
|
+
const messages = errorMessages[localeOf(locale)];
|
|
552
|
+
return Object.hasOwn(messages, code) ? messages[code] : messages.fallback;
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/catalog.ts
|
|
556
|
+
const feature = (type, options) => options?.description === void 0 ? { type } : {
|
|
557
|
+
type,
|
|
558
|
+
description: options.description
|
|
559
|
+
};
|
|
560
|
+
/** Sayısal limit: negatif olmayan tam sayı ya da `null` (sınırsız). */
|
|
561
|
+
const limit = (options) => feature("limit", options);
|
|
562
|
+
/** Açık/kapalı özellik. */
|
|
563
|
+
const flag = (options) => feature("boolean", options);
|
|
564
|
+
/** Serbest metin ayarı. */
|
|
565
|
+
const text = (options) => feature("string", options);
|
|
566
|
+
function matches(type, value) {
|
|
567
|
+
switch (type) {
|
|
568
|
+
case "limit": return value === null || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
569
|
+
case "boolean": return typeof value === "boolean";
|
|
570
|
+
case "string": return typeof value === "string";
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const EXPECTED = {
|
|
574
|
+
limit: "negatif olmayan tam sayı ya da null",
|
|
575
|
+
boolean: "boolean",
|
|
576
|
+
string: "string"
|
|
577
|
+
};
|
|
578
|
+
const show = (value) => value === void 0 ? "yok" : JSON.stringify(value);
|
|
579
|
+
const has = (object, key) => Object.hasOwn(object, key);
|
|
580
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
581
|
+
function invalid(problems) {
|
|
582
|
+
return new StewardConfigError({
|
|
583
|
+
code: "invalid_catalog",
|
|
584
|
+
message: `defineCatalog: katalog geçersiz\n${problems.map((p) => ` - ${p}`).join("\n")}`
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function defineCatalog(definition) {
|
|
588
|
+
const problems = [];
|
|
589
|
+
const input = definition;
|
|
590
|
+
const features = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const [key, spec] of Object.entries(isObject(input.features) ? input.features : {})) if (isObject(spec) && typeof spec.type === "string" && has(EXPECTED, spec.type)) {
|
|
592
|
+
const type = spec.type;
|
|
593
|
+
features.set(key, typeof spec.description === "string" ? {
|
|
594
|
+
type,
|
|
595
|
+
description: spec.description
|
|
596
|
+
} : { type });
|
|
597
|
+
} else problems.push(`features.${key}: limit() / flag() / text() bekleniyor`);
|
|
598
|
+
if (!isObject(input.features)) problems.push("features: nesne bekleniyor");
|
|
599
|
+
if (!isObject(input.plans)) problems.push("plans: nesne bekleniyor");
|
|
600
|
+
if (!Array.isArray(input.prices)) problems.push("prices: dizi bekleniyor");
|
|
601
|
+
if (problems.length > 0) throw invalid(problems);
|
|
602
|
+
const plans = /* @__PURE__ */ new Map();
|
|
603
|
+
for (const [code, plan] of Object.entries(input.plans)) {
|
|
604
|
+
const given = isObject(plan) && isObject(plan.entitlements) ? plan.entitlements : {};
|
|
605
|
+
const entitlements = {};
|
|
606
|
+
for (const [key, { type }] of features) {
|
|
607
|
+
if (!has(given, key)) problems.push(`plans.${code}.entitlements.${key}: eksik (${EXPECTED[type]} bekleniyor)`);
|
|
608
|
+
else if (!matches(type, given[key])) problems.push(`plans.${code}.entitlements.${key}: ${EXPECTED[type]} bekleniyor, ${show(given[key])} geldi`);
|
|
609
|
+
entitlements[key] = given[key];
|
|
610
|
+
}
|
|
611
|
+
for (const key of Object.keys(given).filter((k) => !features.has(k))) problems.push(`plans.${code}.entitlements.${key}: tanımsız feature`);
|
|
612
|
+
const { name, rank, sellable } = isObject(plan) ? plan : {};
|
|
613
|
+
plans.set(code, {
|
|
614
|
+
name,
|
|
615
|
+
rank,
|
|
616
|
+
sellable,
|
|
617
|
+
entitlements
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (typeof input.defaultPlan !== "string" || !plans.has(input.defaultPlan)) problems.push(`defaultPlan: ${show(input.defaultPlan)} planlarda yok`);
|
|
621
|
+
const prices = input.prices.map((price, i) => {
|
|
622
|
+
const copy = { ...isObject(price) ? price : {} };
|
|
623
|
+
if (typeof copy.plan !== "string" || !plans.has(copy.plan)) problems.push(`prices.${i}.plan: ${show(copy.plan)} planlarda yok`);
|
|
624
|
+
return copy;
|
|
625
|
+
});
|
|
626
|
+
if (problems.length > 0) throw invalid(problems);
|
|
627
|
+
const toSyncInput = () => ({
|
|
628
|
+
defaultPlanCode: input.defaultPlan,
|
|
629
|
+
features: [...features].map(([key, spec]) => ({
|
|
630
|
+
key,
|
|
631
|
+
...spec
|
|
632
|
+
})),
|
|
633
|
+
plans: [...plans].map(([code, plan]) => ({
|
|
634
|
+
code,
|
|
635
|
+
...plan,
|
|
636
|
+
entitlements: { ...plan.entitlements }
|
|
637
|
+
})),
|
|
638
|
+
prices: prices.map(({ plan, ...rest }) => ({
|
|
639
|
+
planCode: plan,
|
|
640
|
+
...rest
|
|
641
|
+
}))
|
|
642
|
+
});
|
|
643
|
+
const parsed = CatalogSyncInputSchema.safeParse(toSyncInput());
|
|
644
|
+
if (!parsed.success) {
|
|
645
|
+
const codes = {
|
|
646
|
+
features: [...features.keys()],
|
|
647
|
+
plans: [...plans.keys()]
|
|
648
|
+
};
|
|
649
|
+
throw invalid(parsed.error.issues.map((issue) => {
|
|
650
|
+
const [head, index, ...rest] = issue.path;
|
|
651
|
+
return `${((head === "plans" || head === "features") && typeof index === "number" ? [
|
|
652
|
+
head,
|
|
653
|
+
codes[head][index],
|
|
654
|
+
...rest
|
|
655
|
+
] : issue.path).map(String).join(".") || "(kök)"}: ${issue.message}`;
|
|
656
|
+
}));
|
|
657
|
+
}
|
|
658
|
+
const defaultPlan = input.defaultPlan;
|
|
659
|
+
const isPlan = (code) => typeof code === "string" && plans.has(code);
|
|
660
|
+
const planEntitlements = (code) => plans.get(code).entitlements;
|
|
661
|
+
const entitlementsOf = (plan) => {
|
|
662
|
+
if (!isPlan(plan)) throw new StewardConfigError({
|
|
663
|
+
code: "unknown_plan",
|
|
664
|
+
message: `katalogda plan yok: ${show(plan)}`
|
|
665
|
+
});
|
|
666
|
+
return { ...planEntitlements(plan) };
|
|
667
|
+
};
|
|
668
|
+
const resolve = (state, options = {}) => {
|
|
669
|
+
const fallback = planEntitlements(isPlan(state?.planCode) ? state.planCode : defaultPlan);
|
|
670
|
+
if (!state) return { ...fallback };
|
|
671
|
+
const given = isObject(state.entitlements) ? state.entitlements : {};
|
|
672
|
+
const result = {};
|
|
673
|
+
const fellBack = [];
|
|
674
|
+
for (const [key, { type }] of features) if (has(given, key) && matches(type, given[key])) result[key] = given[key];
|
|
675
|
+
else {
|
|
676
|
+
result[key] = fallback[key];
|
|
677
|
+
fellBack.push(key);
|
|
678
|
+
}
|
|
679
|
+
if (fellBack.length > 0) options.onFallback?.(fellBack);
|
|
680
|
+
return result;
|
|
681
|
+
};
|
|
682
|
+
const stateOf = (ref, plan = defaultPlan, options = {}) => ({
|
|
683
|
+
ref,
|
|
684
|
+
displayName: options.displayName ?? ref,
|
|
685
|
+
version: 0,
|
|
686
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
687
|
+
catalogVersion: null,
|
|
688
|
+
planCode: plan,
|
|
689
|
+
accessState: options.accessState ?? "active",
|
|
690
|
+
entitlements: entitlementsOf(plan),
|
|
691
|
+
subscription: null,
|
|
692
|
+
openCheckout: null,
|
|
693
|
+
profile: {
|
|
694
|
+
present: false,
|
|
695
|
+
kind: null
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
const frozen = (value) => Object.freeze(value);
|
|
699
|
+
return frozen({
|
|
700
|
+
defaultPlan,
|
|
701
|
+
features: frozen(Object.fromEntries([...features].map(([key, spec]) => [key, frozen({ ...spec })]))),
|
|
702
|
+
plans: frozen(Object.fromEntries([...plans].map(([code, plan]) => [code, frozen({
|
|
703
|
+
...plan,
|
|
704
|
+
entitlements: frozen({ ...plan.entitlements })
|
|
705
|
+
})]))),
|
|
706
|
+
prices: frozen(prices.map((price) => frozen({ ...price }))),
|
|
707
|
+
$plan: void 0,
|
|
708
|
+
$entitlements: void 0,
|
|
709
|
+
toSyncInput,
|
|
710
|
+
entitlementsOf,
|
|
711
|
+
resolve,
|
|
712
|
+
isPlan,
|
|
713
|
+
stateOf
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
//#endregion
|
|
717
|
+
//#region src/money.ts
|
|
718
|
+
function assertInteger(name, value) {
|
|
719
|
+
if (!Number.isInteger(value) || value < 0) throw new RangeError(`${name} negatif olmayan tam sayı olmalı: ${value}`);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Vergi dahil fiyatta toplam korunur, matrah kuruşa yuvarlanır ve vergi kalandır;
|
|
723
|
+
* vergi hariç fiyatta vergi kuruşa yuvarlanıp üstüne eklenir.
|
|
724
|
+
*/
|
|
725
|
+
function taxBreakdown(price) {
|
|
726
|
+
const { amountMinor, taxRateBps } = price;
|
|
727
|
+
assertInteger("amountMinor", amountMinor);
|
|
728
|
+
assertInteger("taxRateBps", taxRateBps);
|
|
729
|
+
if (price.taxInclusive) {
|
|
730
|
+
const subtotalMinor = Math.round(amountMinor * 1e4 / (1e4 + taxRateBps));
|
|
731
|
+
return {
|
|
732
|
+
subtotalMinor,
|
|
733
|
+
taxMinor: amountMinor - subtotalMinor,
|
|
734
|
+
totalMinor: amountMinor
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const taxMinor = Math.round(amountMinor * taxRateBps / 1e4);
|
|
738
|
+
return {
|
|
739
|
+
subtotalMinor: amountMinor,
|
|
740
|
+
taxMinor,
|
|
741
|
+
totalMinor: amountMinor + taxMinor
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const TAX_LABEL = {
|
|
745
|
+
tr: {
|
|
746
|
+
inclusive: "KDV dahil",
|
|
747
|
+
exclusive: "KDV hariç"
|
|
748
|
+
},
|
|
749
|
+
en: {
|
|
750
|
+
inclusive: "VAT included",
|
|
751
|
+
exclusive: "VAT excluded"
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Tutar, para biriminin ondalık hanesiyle (TRY/USD 2, JPY 0): `₺1.250,00`
|
|
756
|
+
* (tr), `₺1,250.00` (en). Sembol dar biçimde (`TRY 1,250.00` değil).
|
|
757
|
+
*/
|
|
758
|
+
function formatMoney(amountMinor, currency, locale) {
|
|
759
|
+
if (!Number.isInteger(amountMinor)) throw new RangeError(`amountMinor tam sayı olmalı: ${amountMinor}`);
|
|
760
|
+
const digits = new Intl.NumberFormat("en", {
|
|
761
|
+
style: "currency",
|
|
762
|
+
currency
|
|
763
|
+
}).resolvedOptions().maximumFractionDigits ?? 2;
|
|
764
|
+
return new Intl.NumberFormat(locale, {
|
|
765
|
+
style: "currency",
|
|
766
|
+
currency,
|
|
767
|
+
currencyDisplay: "narrowSymbol",
|
|
768
|
+
minimumFractionDigits: digits,
|
|
769
|
+
maximumFractionDigits: digits
|
|
770
|
+
}).format(amountMinor / 10 ** digits);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Fiyat etiketi: `₺1.250,00 · KDV dahil` (tr), `₺1,250.00 · VAT included` (en).
|
|
774
|
+
* Vergi oranı 0 ise vergi ibaresi yazılmaz. Sayı biçimi `locale`'in kendisiyle,
|
|
775
|
+
* ibare `tr*` → Türkçe, diğerleri İngilizce.
|
|
776
|
+
*/
|
|
777
|
+
function formatPrice(price, locale) {
|
|
778
|
+
const amount = formatMoney(price.amountMinor, price.currency, locale);
|
|
779
|
+
if (price.taxRateBps === 0) return amount;
|
|
780
|
+
const labels = TAX_LABEL[localeOf(locale)];
|
|
781
|
+
return `${amount} · ${price.taxInclusive ? labels.inclusive : labels.exclusive}`;
|
|
782
|
+
}
|
|
783
|
+
//#endregion
|
|
784
|
+
export { ACCOUNT_INCLUDES, API_VERSION, AccessStateSchema, AccountDeleteResponseSchema, AccountDetailSchema, AccountDunningStateSchema, AccountOpenCheckoutSchema, AccountProfileStateSchema, AccountScheduledChangeSchema, AccountSchema, AccountStateSchema, AccountSubscriptionStateSchema, AccountUpsertInputSchema, AccountUpsertResponseSchema, AccountsRefreshResultSchema, ActorRefSchema, AmountMinorSchema, AnyBillingEventSchema, ApiVersionSchema, BillingEventSchema, BillingProfileKindSchema, BillingProfileResponseSchema, BillingProfileSchema, BrandingPatchSchema, BrandingSchema, CHECKOUT_ERRORS, CancelSubscriptionInputSchema, CancelSubscriptionResponseSchema, CatalogSchema, CatalogSyncInputSchema, CatalogSyncResultSchema, CheckoutConsentInputSchema, CheckoutPrefillSchema, CheckoutSessionCreateInputSchema, CheckoutSessionCreatedSchema, CheckoutSessionResponseSchema, CheckoutSessionSchema, CheckoutStatusSchema, CodeSchema, ConsentSchema, CurrencySchema, DEFAULT_CHECKOUT_TTL_MINUTES, DEFAULT_ENDPOINT_EVENT_TYPES, DEFAULT_PORTAL_TTL_MINUTES, DOCTOR_PENDING_BACKLOG_MS, DOCTOR_PROVIDER_ENVIRONMENTS, DOCTOR_REQUIRED_BRANDING, DOCTOR_REQUIRED_LEGAL_LINKS, DOCTOR_SEVERITIES, DateTimeSchema, DeliverySchema, DoctorCatalogSchema, DoctorEndpointSchema, DoctorHostedSchema, DoctorProviderSchema, DoctorReportSchema, DoctorSettingsSchema, DoctorSeveritySchema, DoctorUnsellablePriceSchema, DoctorWarningSchema, ERROR_CATEGORIES, ERROR_CODES, EVENT_DETAIL_SCHEMAS, EVENT_TYPES, EntitlementSnapshotSchema, EntitlementValueSchema, EntitlementsSchema, ErrorCategorySchema, ErrorResponseSchema, EventIdSchema, EventListSchema, EventRecordSchema, EventTypeSchema, ExternalRefSchema, FeatureDefSchema, FeatureTypeSchema, GrantCreateInputSchema, GrantCreateResponseSchema, GrantSchema, HEADER_ACTOR_REF, HEADER_API_VERSION, HEADER_IDEMPOTENCY_KEY, HEADER_REQUEST_ID, HEADER_SDK, IdSchema, IntervalSchema, InvoiceIssueInputSchema, InvoiceIssueResponseSchema, InvoiceLineSchema, InvoiceSchema, InvoiceStatusSchema, InvoiceVoidInputSchema, InvoiceVoidResponseSchema, LEGACY_EVENT_TYPES, LIVE_SUBSCRIPTION_STATUSES, MANUAL_INVOICE_MAX_AMOUNT_MINOR, MAX_CHECKOUT_CONSENT_DOCS, ManualInvoiceInputSchema, ManualInvoiceLineInputSchema, ManualInvoiceResponseSchema, MetadataSchema, PING_EVENT_TYPE, PLAN_SET_MODES, PORTAL_SESSION_ERRORS, PaymentSchema, PingSchema, PlanDefSchema, PlanSetInputSchema, PlanSetModeSchema, PlanSetResponseSchema, PlanSetWarningSchema, PortalSessionCreateInputSchema, PortalSessionSchema, PriceDefSchema, PriceSchema, ProductSettingsPatchSchema, ProductSettingsSchema, ProviderPayloadSchema, ResyncResultSchema, ReturnOriginSchema, SETTINGS_LOCALES, STEWARD_ERROR_CODES, SettingsLocaleSchema, StatsSchema, StewardApiError, StewardConfigError, StewardContractError, StewardError, StewardNetworkError, SubscriptionSchema, SubscriptionStatusSchema, SubscriptionSummarySchema, TtlMinutesSchema, WEBHOOK_ENDPOINT_STATUSES, WEBHOOK_SECRET_PREFIX, WebUrlSchema, WebhookEndpointCreateInputSchema, WebhookEndpointListSchema, WebhookEndpointRotateInputSchema, WebhookEndpointSchema, WebhookEndpointStatusSchema, WebhookLastDeliverySchema, WebhookSecretSchema, applySettingsPatch, buildDoctorReport, checkoutRedirectUrl, createBillingClient, createCatalogCache, createSteward, defineCatalog, endpointDiagnosisOf, errorCodeSpec, errorMessages, flag, formatMoney, formatPrice, generateWebhookSecret, hostedReadinessOf, httpStatusFor, isE164, isEventType, isGrantActive, isKnownErrorCode, isLegacyCheckoutInput, isLiveSubscriptionStatus, isReturnUrlAllowed, isValidTckn, isValidVkn, limit, manualInvoiceTotals, messageFor, parseRetryAfter, resolveEntitlements, returnOriginsOf, splitInclusiveTax, stateCache, taxBreakdown, text, webhookEndpointUrlProblem };
|