@receiz/sdk 97.0.0 → 97.1.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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildReceizIdContinueRequest, createReceizIdIdentity, projectReceizIdentityAccount, readReceizIdentityArtifact, signReceizIdentityLoginProof, verifyReceizIdentityLoginProof, } from "./identity.js";
2
2
  export * from "./identity.js";
3
- export const RECEIZ_SDK_VERSION = "97.0.0";
3
+ export const RECEIZ_SDK_VERSION = "97.1.0";
4
4
  export const RECEIZ_DEFAULT_BASE_URL = "https://receiz.com";
5
5
  export const RECEIZ_WEBHOOK_SIGNATURE_HEADER = "x-receiz-signature";
6
6
  export const RECEIZ_WEBHOOK_TIMESTAMP_HEADER = "x-receiz-timestamp";
@@ -200,6 +200,180 @@ export function createReceizAppStateRecord(input) {
200
200
  };
201
201
  return record;
202
202
  }
203
+ export function createReceizAppStateFeed(records, options = {}) {
204
+ const feed = {
205
+ schema: options.schema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
206
+ records: records.map((record) => createReceizAppStateRecord(record)),
207
+ };
208
+ if (options.namespace)
209
+ feed.namespace = options.namespace;
210
+ if (options.externalCreatorId)
211
+ feed.externalCreatorId = options.externalCreatorId;
212
+ return feed;
213
+ }
214
+ export function createReceizPublicStoreStateRecord(input) {
215
+ return createReceizAppStateRecord({
216
+ ...input,
217
+ platform: input.platform ?? "Receiz.app Commerce Cloud",
218
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
219
+ });
220
+ }
221
+ export function createReceizTenantAppStateRecord(input) {
222
+ const sourceUrl = input.sourceUrl ?? receizAppStateUrlFromHost(input.tenantHost);
223
+ const data = input.data ?? {
224
+ ...input.state,
225
+ ...(input.schemaVersion !== undefined ? { schemaVersion: input.schemaVersion } : {}),
226
+ tenantHost: normalizeReceizHost(input.tenantHost),
227
+ };
228
+ const record = {
229
+ sourceUrl,
230
+ state: input.projectionState ?? (input.visibility === "private" ? "private" : "published"),
231
+ platform: input.platform ?? "Receiz App Runtime",
232
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
233
+ data,
234
+ };
235
+ if (input.id)
236
+ record.id = input.id;
237
+ const externalCreatorId = input.externalCreatorId ?? input.creatorReceizId;
238
+ if (externalCreatorId)
239
+ record.externalCreatorId = externalCreatorId;
240
+ if (input.title)
241
+ record.title = input.title;
242
+ record.namespace = input.namespace ?? receizAppStateNamespaceFromUrl(sourceUrl);
243
+ record.record = input.record ?? input.state;
244
+ return createReceizAppStateRecord(record);
245
+ }
246
+ function isReceizAppStateFeed(value) {
247
+ return isRecord(value) && Array.isArray(value.records);
248
+ }
249
+ export function normalizeReceizHost(hostOrUrl) {
250
+ const trimmed = hostOrUrl.trim();
251
+ if (!trimmed)
252
+ throw new ReceizValidationError("receiz.domains.normalizeHost", ["host is required"]);
253
+ try {
254
+ return new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`).hostname.toLowerCase();
255
+ }
256
+ catch {
257
+ return trimmed.replace(/^https?:\/\//i, "").split(/[/?#]/)[0]?.toLowerCase() ?? trimmed.toLowerCase();
258
+ }
259
+ }
260
+ function bodyWithIdempotency(body, idempotencyKey) {
261
+ if (!idempotencyKey || Object.prototype.hasOwnProperty.call(body, "idempotencyKey"))
262
+ return body;
263
+ return { ...body, idempotencyKey };
264
+ }
265
+ function headersWithIdempotency(headers, idempotencyKey) {
266
+ const next = new Headers(headers);
267
+ if (idempotencyKey)
268
+ next.set("idempotency-key", idempotencyKey);
269
+ return next;
270
+ }
271
+ function usdToCents(value) {
272
+ const trimmed = value.trim();
273
+ if (!/^\d+(\.\d{1,2})?$/.test(trimmed))
274
+ throw new ReceizValidationError("receiz.commerce.amountUsd", ["amountUsd must be a positive USD decimal"]);
275
+ const [whole = "0", fraction = ""] = trimmed.split(".");
276
+ return BigInt(whole) * 100n + BigInt((fraction + "00").slice(0, 2));
277
+ }
278
+ function centsToUsd(value) {
279
+ const sign = value < 0n ? "-" : "";
280
+ const abs = value < 0n ? -value : value;
281
+ return `${sign}${abs / 100n}.${String(abs % 100n).padStart(2, "0")}`;
282
+ }
283
+ function stringToCents(value) {
284
+ if (typeof value === "number" && Number.isFinite(value))
285
+ return BigInt(Math.max(0, Math.trunc(value)));
286
+ if (typeof value === "bigint")
287
+ return value < 0n ? 0n : value;
288
+ if (typeof value === "string" && /^\d+$/.test(value.trim()))
289
+ return BigInt(value.trim());
290
+ return 0n;
291
+ }
292
+ function sandboxProducts(count) {
293
+ return Array.from({ length: Math.max(0, count) }, (_, index) => ({
294
+ id: `sandbox_product_${index + 1}`,
295
+ title: `Sandbox Product ${index + 1}`,
296
+ amountUsd: centsToUsd(BigInt(index + 1) * 1200n),
297
+ proofObject: {
298
+ schema: "receiz.sdk.sandbox.proof_object.v1",
299
+ objectId: `sandbox_product_${index + 1}`,
300
+ },
301
+ }));
302
+ }
303
+ export async function verifyReceizWebhookRequest(request, options) {
304
+ const signature = request.headers.get(RECEIZ_WEBHOOK_SIGNATURE_HEADER) ?? "";
305
+ const timestamp = request.headers.get(RECEIZ_WEBHOOK_TIMESTAMP_HEADER) ?? "";
306
+ const body = await request.clone().text();
307
+ const verifyInput = {
308
+ secret: options.secret,
309
+ timestamp,
310
+ body,
311
+ signature,
312
+ };
313
+ if (options.toleranceSeconds !== undefined)
314
+ verifyInput.toleranceSeconds = options.toleranceSeconds;
315
+ if (options.nowMs !== undefined)
316
+ verifyInput.nowMs = options.nowMs;
317
+ return verifyReceizWebhookSignature(verifyInput);
318
+ }
319
+ function resolveReceizAppStateProjectionRecord(input) {
320
+ if (!input || !isRecord(input))
321
+ return null;
322
+ const looksLikeResponse = typeof input.ok === "boolean" &&
323
+ Object.prototype.hasOwnProperty.call(input, "record") &&
324
+ !Object.prototype.hasOwnProperty.call(input, "sourceUrl") &&
325
+ !Object.prototype.hasOwnProperty.call(input, "data");
326
+ if (looksLikeResponse) {
327
+ const response = input;
328
+ return response.record && isRecord(response.record) ? response.record : null;
329
+ }
330
+ return input;
331
+ }
332
+ function receizAppStateRestoreError(record, options) {
333
+ if (!record)
334
+ return "app_state_not_found";
335
+ if (options.schema && record.schema !== options.schema)
336
+ return "app_state_schema_mismatch";
337
+ if (options.state && record.state !== options.state)
338
+ return "app_state_state_mismatch";
339
+ if (!isRecord(record.data))
340
+ return "app_state_data_missing";
341
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey)) {
342
+ return "app_state_required_data_missing";
343
+ }
344
+ return "app_state_data_unavailable";
345
+ }
346
+ export function extractReceizAppStateData(input, options = {}) {
347
+ const record = resolveReceizAppStateProjectionRecord(input);
348
+ if (!record)
349
+ return null;
350
+ if (options.schema && record.schema !== options.schema)
351
+ return null;
352
+ if (options.state && record.state !== options.state)
353
+ return null;
354
+ if (!isRecord(record.data))
355
+ return null;
356
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey))
357
+ return null;
358
+ return record.data;
359
+ }
360
+ export function restoreReceizAppStateProjection(input, options = {}) {
361
+ const record = resolveReceizAppStateProjectionRecord(input);
362
+ const data = extractReceizAppStateData(input, options);
363
+ if (!record || !data) {
364
+ return {
365
+ ok: false,
366
+ record,
367
+ data: null,
368
+ error: receizAppStateRestoreError(record, options),
369
+ };
370
+ }
371
+ return {
372
+ ok: true,
373
+ record,
374
+ data,
375
+ };
376
+ }
203
377
  function isRecord(value) {
204
378
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
205
379
  }
@@ -367,6 +541,8 @@ export class ReceizClient {
367
541
  baseUrl;
368
542
  accessToken;
369
543
  fetchImpl;
544
+ doctor = (options = {}) => this.runDoctor(options);
545
+ capabilities = (options = {}) => this.inspectCapabilities(options);
370
546
  verification = {
371
547
  verifyArtifact: (file) => this.verifyArtifact(file),
372
548
  sealArtifact: (file, options) => this.sealArtifact(file, options),
@@ -380,13 +556,26 @@ export class ReceizClient {
380
556
  registryFeed: (feed, options = {}) => this.publishAppState(feed, options),
381
557
  };
382
558
  appState = {
383
- publish: (feed, options = {}) => this.publishAppState(feed, options),
559
+ publish: (input, options = {}) => this.publishAppStateInput(input, options),
384
560
  publishRecord: (record, options = {}) => this.publishAppStateRecord(record, options),
561
+ resolve: (input) => this.resolveAppState(input),
385
562
  byUrl: (url) => this.readAppStateByUrl(url),
386
563
  byHost: (host) => this.readAppStateByHost(host),
387
564
  byCreator: (receizId) => this.readAppStateByCreator(receizId),
388
565
  byNamespace: (namespace) => this.readAppStateByNamespace(namespace),
389
566
  byId: (id) => this.readAppStateById(id),
567
+ restoreByUrl: (url, options = {}) => this.restoreAppStateByUrl(url, options),
568
+ restoreByHost: (host, options = {}) => this.restoreAppStateByHost(host, options),
569
+ restoreById: (id, options = {}) => this.restoreAppStateById(id, options),
570
+ createRecord: createReceizAppStateRecord,
571
+ createTenantRecord: createReceizTenantAppStateRecord,
572
+ createFeed: createReceizAppStateFeed,
573
+ createPublicStoreRecord: createReceizPublicStoreStateRecord,
574
+ urlFromHost: receizAppStateUrlFromHost,
575
+ namespaceFromUrl: receizAppStateNamespaceFromUrl,
576
+ objectIdFromUrl: receizAppStateObjectIdFromUrl,
577
+ extractData: extractReceizAppStateData,
578
+ restoreProjection: restoreReceizAppStateProjection,
390
579
  };
391
580
  sports = {
392
581
  conformance: () => this.sportsConformance(),
@@ -488,12 +677,51 @@ export class ReceizClient {
488
677
  bootstrap: (username) => this.request(`/api/connect/login/bootstrap/${encodePathSegment(username)}`),
489
678
  authorizeUrl: (options) => this.authorizeUrl(options),
490
679
  };
680
+ commerce = {
681
+ oneClickCheckout: (body) => this.oneClickCheckout(body),
682
+ };
491
683
  payments = {
492
684
  embeddedCheckout: (body) => this.request("/api/payments/embed/checkout", { method: "POST", body }),
493
685
  embeddedNoteClaim: (body) => this.request("/api/payments/embed/note-claim", { method: "POST", body }),
494
686
  };
687
+ media = {
688
+ upload: (file, options = {}) => this.uploadMedia(file, options),
689
+ objectUrl: (query) => appendQuery(`${this.baseUrl}/api/storage/object`, query),
690
+ };
691
+ domains = {
692
+ normalizeHost: normalizeReceizHost,
693
+ tenantUrl: receizAppStateUrlFromHost,
694
+ namespace: receizAppStateNamespaceFromUrl,
695
+ objectId: receizAppStateObjectIdFromUrl,
696
+ resolveTenant: (host, options = {}) => this.restoreAppStateByHost(host, options),
697
+ reserveSubdomain: (body, options = {}) => this.delegated("/api/connect/domains/subdomains/reserve", {
698
+ method: "POST",
699
+ body: bodyWithIdempotency(body, options.idempotencyKey),
700
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
701
+ }),
702
+ verifyCustomDomain: (body, options = {}) => this.delegated("/api/connect/domains/custom/verify", {
703
+ method: "POST",
704
+ body: bodyWithIdempotency(body, options.idempotencyKey),
705
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
706
+ }),
707
+ status: (query = {}) => this.delegated(appendQuery("/api/connect/domains/status", query)),
708
+ };
709
+ events = {
710
+ subscribe: (body) => this.eventSubscribe(body),
711
+ };
712
+ proof = {
713
+ query: (query) => this.delegated("/api/connect/proof/query", { method: "POST", body: query }),
714
+ };
715
+ jobs = {
716
+ enqueue: (body) => this.delegated("/api/connect/jobs", {
717
+ method: "POST",
718
+ body: bodyWithIdempotency(body, body.idempotencyKey),
719
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
720
+ }),
721
+ };
495
722
  webhooks = {
496
723
  sign: createReceizWebhookSignature,
724
+ verify: verifyReceizWebhookRequest,
497
725
  verifySignature: verifyReceizWebhookSignature,
498
726
  signaturePayload: buildReceizWebhookSignaturePayload,
499
727
  assertEvent: assertReceizWebhookEvent,
@@ -513,6 +741,33 @@ export class ReceizClient {
513
741
  assetManifest: projectReceizAssetManifest,
514
742
  sportsCardManifest: projectReceizSportsCardManifest,
515
743
  };
744
+ sandbox = {
745
+ seedStore: (options) => {
746
+ const tenantHost = normalizeReceizHost(options.tenantHost);
747
+ return {
748
+ ok: true,
749
+ schema: "receiz.sdk.sandbox.store.v1",
750
+ tenantHost,
751
+ state: {
752
+ brand: tenantHost,
753
+ products: sandboxProducts(options.products ?? 3),
754
+ rewards: [{ id: "sandbox_reward_1", title: "Sandbox reward", type: "credit" }],
755
+ },
756
+ };
757
+ },
758
+ wallet: (options = {}) => ({
759
+ ok: true,
760
+ userId: "sandbox_customer",
761
+ balanceUsdCents: options.balanceUsdCents ?? "10000",
762
+ balancePhiMicro: options.balancePhiMicro ?? "100000000",
763
+ }),
764
+ checkout: (options) => ({
765
+ ok: options.outcome !== "failure",
766
+ checkoutSessionId: "sandbox_checkout",
767
+ status: options.outcome === "failure" ? "failed" : "succeeded",
768
+ amountUsd: options.amountUsd,
769
+ }),
770
+ };
516
771
  proofMemory = {
517
772
  createRegister: createReceizProofRegister,
518
773
  createMemory: createReceizProofMemory,
@@ -572,22 +827,124 @@ export class ReceizClient {
572
827
  async readPublicProofByUrl(url) {
573
828
  return this.request(appendQuery("/api/public-proof/by-url", { url }));
574
829
  }
830
+ async inspectCapabilities(options = {}) {
831
+ const tenantHost = options.tenantHost ? normalizeReceizHost(options.tenantHost) : undefined;
832
+ const scopes = options.scopes ?? [];
833
+ const delegatedAvailable = Boolean(this.accessToken);
834
+ const configured = (available, reason) => ({
835
+ available,
836
+ status: available ? "available" : "missing",
837
+ ...(reason ? { reason } : {}),
838
+ ...(!available ? { fixUrl: `${this.baseUrl}/developers` } : {}),
839
+ });
840
+ return {
841
+ ok: true,
842
+ schema: "receiz.sdk.capabilities.v1",
843
+ baseUrl: this.baseUrl,
844
+ ...(tenantHost ? { tenantHost } : {}),
845
+ scopes,
846
+ capabilities: {
847
+ identity: configured(true),
848
+ wallet: configured(delegatedAvailable, "Connect bearer token required for wallet reads."),
849
+ payments: configured(delegatedAvailable, "Connect bearer token required for checkout/session routes."),
850
+ proofStore: configured(true),
851
+ media: configured(delegatedAvailable, "Connect bearer token required for media uploads."),
852
+ domains: configured(Boolean(tenantHost), "tenantHost is required for tenant domain helpers."),
853
+ twin: configured(delegatedAvailable, "Connect bearer token required for Twin routes."),
854
+ commerce: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for commerce helpers."),
855
+ rewards: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for reward routes."),
856
+ events: configured(delegatedAvailable, "Connect bearer token required for event subscriptions."),
857
+ search: configured(delegatedAvailable, "Connect bearer token required for private proof search."),
858
+ permissions: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for tenant permissions."),
859
+ jobs: configured(delegatedAvailable, "Connect bearer token required for durable jobs."),
860
+ },
861
+ };
862
+ }
863
+ async runDoctor(options = {}) {
864
+ const capabilities = await this.inspectCapabilities(options);
865
+ const missing = [];
866
+ const warnings = [];
867
+ const fixes = [];
868
+ if (!this.accessToken) {
869
+ missing.push({
870
+ code: "missing_access_token",
871
+ message: "Create the Receiz client with a delegated Connect/OIDC bearer token before using wallet, media, commerce, events, domains, or jobs.",
872
+ fixUrl: `${this.baseUrl}/developers/connect`,
873
+ });
874
+ }
875
+ if (!options.tenantHost) {
876
+ warnings.push({
877
+ code: "missing_tenant_host",
878
+ message: "Pass tenantHost so app-state, commerce, domain, permission, and customer-session helpers can scope correctly.",
879
+ fixUrl: `${this.baseUrl}/developers/app-state`,
880
+ });
881
+ }
882
+ if (!options.callbackUrl) {
883
+ warnings.push({
884
+ code: "missing_callback_url",
885
+ message: "Pass callbackUrl to verify your Connect/OIDC redirect configuration before users hit the authorize screen.",
886
+ fixUrl: `${this.baseUrl}/developers/connect`,
887
+ });
888
+ }
889
+ const scopes = new Set(options.scopes ?? []);
890
+ for (const requiredScope of ["openid", "profile", "receiz:record"]) {
891
+ if (!scopes.has(requiredScope)) {
892
+ warnings.push({
893
+ code: "missing_scope",
894
+ message: `Recommended scope missing: ${requiredScope}.`,
895
+ fixUrl: `${this.baseUrl}/developers/apps`,
896
+ });
897
+ }
898
+ }
899
+ fixes.push(...missing, ...warnings);
900
+ return {
901
+ ok: missing.length === 0,
902
+ schema: "receiz.sdk.doctor.v1",
903
+ baseUrl: this.baseUrl,
904
+ ...(capabilities.tenantHost ? { tenantHost: capabilities.tenantHost } : {}),
905
+ capabilities: capabilities.capabilities,
906
+ missing,
907
+ warnings,
908
+ fixes,
909
+ };
910
+ }
575
911
  async publishAppState(feed, options = {}) {
576
- const requestOptions = { method: "POST", body: feed };
577
- if (options.signature)
578
- requestOptions.headers = { "x-public-proof-feed-signature": options.signature };
912
+ const requestOptions = {
913
+ method: "POST",
914
+ body: feed,
915
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
916
+ };
917
+ if (options.signature) {
918
+ const headers = headersWithIdempotency(requestOptions.headers, options.idempotencyKey);
919
+ headers.set("x-public-proof-feed-signature", options.signature);
920
+ requestOptions.headers = headers;
921
+ }
579
922
  return this.request("/api/public-proof/registry/feed", requestOptions);
580
923
  }
924
+ async publishAppStateInput(input, options = {}) {
925
+ if (isReceizAppStateFeed(input)) {
926
+ return this.publishAppState(input, options);
927
+ }
928
+ const record = createReceizTenantAppStateRecord(input);
929
+ const publishOptions = {};
930
+ if (options.signature)
931
+ publishOptions.signature = options.signature;
932
+ const idempotencyKey = options.idempotencyKey ?? input.idempotencyKey;
933
+ if (idempotencyKey)
934
+ publishOptions.idempotencyKey = idempotencyKey;
935
+ const feedOptions = { schema: RECEIZ_PUBLIC_STORE_STATE_FEED_SCHEMA };
936
+ if (record.namespace)
937
+ feedOptions.namespace = record.namespace;
938
+ if (record.externalCreatorId)
939
+ feedOptions.externalCreatorId = record.externalCreatorId;
940
+ return this.publishAppState(createReceizAppStateFeed([record], feedOptions), publishOptions);
941
+ }
581
942
  async publishAppStateRecord(record, options = {}) {
582
- const appStateRecord = createReceizAppStateRecord(record);
583
- const feed = {
943
+ const feed = createReceizAppStateFeed([record], {
584
944
  schema: options.feedSchema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
585
- records: [appStateRecord],
586
- };
587
- if (options.namespace)
588
- feed.namespace = options.namespace;
589
- if (options.externalCreatorId)
590
- feed.externalCreatorId = options.externalCreatorId;
945
+ ...(options.namespace ? { namespace: options.namespace } : {}),
946
+ ...(options.externalCreatorId ? { externalCreatorId: options.externalCreatorId } : {}),
947
+ });
591
948
  return this.publishAppState(feed, options.signature ? { signature: options.signature } : {});
592
949
  }
593
950
  async readAppStateByUrl(url) {
@@ -605,6 +962,156 @@ export class ReceizClient {
605
962
  async readAppStateById(id) {
606
963
  return this.request(`/api/public-proof/${encodePathSegment(id)}`);
607
964
  }
965
+ async restoreAppStateByUrl(url, options = {}) {
966
+ const response = await this.readAppStateByUrl(url);
967
+ return restoreReceizAppStateProjection(response, options);
968
+ }
969
+ async restoreAppStateByHost(host, options = {}) {
970
+ return this.restoreAppStateByUrl(receizAppStateUrlFromHost(host), options);
971
+ }
972
+ async restoreAppStateById(id, options = {}) {
973
+ try {
974
+ const response = await this.readAppStateById(id);
975
+ return restoreReceizAppStateProjection(response, options);
976
+ }
977
+ catch (error) {
978
+ if (error instanceof ReceizHttpError && error.status === 404) {
979
+ return {
980
+ ok: false,
981
+ record: null,
982
+ data: null,
983
+ error: "app_state_not_found",
984
+ };
985
+ }
986
+ throw error;
987
+ }
988
+ }
989
+ async resolveAppState(input) {
990
+ const options = {
991
+ ...(input.schema ? { schema: input.schema } : {}),
992
+ ...(input.state ? { state: input.state } : {}),
993
+ ...(input.requiredDataKey ? { requiredDataKey: input.requiredDataKey } : {}),
994
+ };
995
+ if (input.id)
996
+ return this.restoreAppStateById(input.id, options);
997
+ if (input.url)
998
+ return this.restoreAppStateByUrl(input.url, options);
999
+ if (input.host || input.tenantHost)
1000
+ return this.restoreAppStateByHost(input.host ?? input.tenantHost ?? "", options);
1001
+ if (input.namespace) {
1002
+ const response = await this.readAppStateByNamespace(input.namespace);
1003
+ const records = response.records;
1004
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1005
+ }
1006
+ const creator = input.creatorReceizId ?? input.receizId;
1007
+ if (creator) {
1008
+ const response = await this.readAppStateByCreator(creator);
1009
+ const records = response.records;
1010
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1011
+ }
1012
+ throw new ReceizValidationError("receiz.appState.resolve", ["id, url, host, tenantHost, namespace, creatorReceizId, or receizId is required"]);
1013
+ }
1014
+ async oneClickCheckout(body) {
1015
+ const tenantHost = normalizeReceizHost(body.tenantHost);
1016
+ const totalUsdCents = usdToCents(body.amountUsd);
1017
+ const walletFirst = body.walletFirst !== false;
1018
+ const cardFallback = body.cardFallback !== false;
1019
+ const wallet = walletFirst ? await this.delegated("/api/connect/wallet/me") : null;
1020
+ const walletAvailable = walletFirst ? stringToCents(wallet?.balanceUsdCents) : 0n;
1021
+ const walletApplied = walletFirst ? (walletAvailable > totalUsdCents ? totalUsdCents : walletAvailable) : 0n;
1022
+ const cardDelta = totalUsdCents - walletApplied;
1023
+ let checkoutSession = null;
1024
+ if (cardDelta > 0n) {
1025
+ if (!cardFallback) {
1026
+ const response = {
1027
+ ok: false,
1028
+ tenantHost,
1029
+ funding: {
1030
+ totalUsdCents: String(totalUsdCents),
1031
+ walletAppliedUsdCents: String(walletApplied),
1032
+ cardDeltaUsdCents: String(cardDelta),
1033
+ walletFirst,
1034
+ cardFallback,
1035
+ },
1036
+ wallet,
1037
+ checkoutSession: null,
1038
+ proofObject: null,
1039
+ events: [{ type: "checkout.card_delta_required", cardDeltaUsdCents: String(cardDelta) }],
1040
+ error: "card_delta_required",
1041
+ };
1042
+ if (body.orderId)
1043
+ response.orderId = body.orderId;
1044
+ return response;
1045
+ }
1046
+ const checkoutBody = {
1047
+ amountUsd: centsToUsd(cardDelta),
1048
+ currency: body.currency ?? "usd",
1049
+ uiMode: "embedded",
1050
+ description: `Receiz one-click checkout for ${tenantHost}`,
1051
+ tenantHost,
1052
+ };
1053
+ if (body.orderId)
1054
+ checkoutBody.referenceId = body.orderId;
1055
+ if (body.customerEmail)
1056
+ checkoutBody.customerEmail = body.customerEmail;
1057
+ if (body.successUrl)
1058
+ checkoutBody.successUrl = body.successUrl;
1059
+ if (body.cancelUrl)
1060
+ checkoutBody.cancelUrl = body.cancelUrl;
1061
+ if (body.cart)
1062
+ checkoutBody.cart = body.cart;
1063
+ checkoutSession = await this.delegated("/api/connect/payments/checkout", {
1064
+ method: "POST",
1065
+ body: bodyWithIdempotency(checkoutBody, body.idempotencyKey),
1066
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1067
+ });
1068
+ }
1069
+ const response = {
1070
+ ok: true,
1071
+ tenantHost,
1072
+ funding: {
1073
+ totalUsdCents: String(totalUsdCents),
1074
+ walletAppliedUsdCents: String(walletApplied),
1075
+ cardDeltaUsdCents: String(cardDelta),
1076
+ walletFirst,
1077
+ cardFallback,
1078
+ },
1079
+ wallet,
1080
+ checkoutSession,
1081
+ proofObject: checkoutSession?.proofBundle ?? null,
1082
+ events: [
1083
+ { type: "wallet.summary.read", walletAppliedUsdCents: String(walletApplied) },
1084
+ ...(checkoutSession ? [{ type: "checkout.session.created", checkoutSessionId: checkoutSession.checkoutSessionId ?? null }] : []),
1085
+ ],
1086
+ };
1087
+ if (body.orderId)
1088
+ response.orderId = body.orderId;
1089
+ return response;
1090
+ }
1091
+ async uploadMedia(file, options = {}) {
1092
+ const form = new FormData();
1093
+ form.set("file", file, options.filename ?? "receiz-media");
1094
+ if (options.tenantHost)
1095
+ form.set("tenantHost", normalizeReceizHost(options.tenantHost));
1096
+ if (options.purpose)
1097
+ form.set("purpose", options.purpose);
1098
+ if (options.metadata)
1099
+ form.set("metadata", JSON.stringify(options.metadata));
1100
+ if (options.idempotencyKey)
1101
+ form.set("idempotencyKey", options.idempotencyKey);
1102
+ return this.delegated("/api/connect/media/upload", {
1103
+ method: "POST",
1104
+ body: form,
1105
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
1106
+ });
1107
+ }
1108
+ async eventSubscribe(body) {
1109
+ return this.delegated("/api/connect/events/subscribe", {
1110
+ method: "POST",
1111
+ body: bodyWithIdempotency(body, body.idempotencyKey),
1112
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1113
+ });
1114
+ }
608
1115
  async sportsConformance() {
609
1116
  return this.request("/api/game/sports/conformance");
610
1117
  }