1import { operation, validate } from "../contract.mjs";2 3// This module runs on the authenticated app backend, which owns the credential.4export function createCommerceClient({ baseUrl, credential }) {5 async function call(name, input) {6 const spec = operation(name);7 if (!spec) throw new Error("Unknown operation");8 if (spec.input && !validate(spec.input, input))9 throw new Error("Invalid operation input");10 const url = new URL(spec.path, baseUrl);11 if (spec.method === "GET" && input)12 for (const [key, value] of Object.entries(input))13 url.searchParams.set(key, value);14 const response = await fetch(url, {15 method: spec.method,16 headers: {17 "content-type": "application/json",18 ...(spec.auth === "none" ? {} : { authorization: credential }),19 },20 ...(spec.method === "POST" ? { body: JSON.stringify(input) } : {}),21 redirect: "error",22 signal: AbortSignal.timeout(5000),23 });24 const result = await response.json();25 if (response.status !== spec.successStatus)26 throw new Error("Commerce operation failed");27 if (!validate(spec.result, result))28 throw new Error("Invalid operation result");29 return result;30 }31 32 async function fulfill(input, { userId, productId }) {33 const evidence = { ...input };34 delete evidence.userId;35 const verdict = await call("verifyPurchase", evidence);36 if (!verdict.isValid) throw new Error("Purchase was not accepted");37 const binding = await call("bindPurchase", { ...evidence, userId });38 if (!binding.bound) throw new Error("Purchase belongs to another user");39 const access = await call("entitlements", { userId });40 if (!access.productIds.includes(productId))41 throw new Error("Requested product is not accessible");42 return access;43 }44 45 return { call, fulfill };46}47