@wix/himalaya-cli 0.1324.0 → 0.1325.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/checkout/src/index.d.ts +6 -10
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/order-refs.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/purchase-fields.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +9 -35
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +30 -13
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +20 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +2890 -54
- package/dist/cli.mjs +4 -4
- package/fixtures/wix-canned.json +61 -6
- package/guides/iap-pricing.md +9 -0
- package/guides/iap-screen-adoption.md +77 -0
- package/guides/importable-sdks.md +2 -0
- package/guides/index.json +6 -0
- package/guides/sdk-reference.json +339 -1
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +1 -1
- package/templates/beauty-salon/dev/index.ts +46 -18
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/beauty-salon/tier5-src/_shared/himi-store-collection.ts +1 -1
- package/templates/beauty-salon/tier5-src/_shared/home-service.ts +2 -1
- package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +48 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/beauty-salon/tier5-src/himi-branded-lite-order/index.ts +24 -3
- package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +24 -3
- package/templates/beauty-salon/tier5-src/himi-store-saved/index.ts +8 -3
- package/templates/beauty-salon/tier5-src/himi-store-shop/index.ts +10 -0
- package/templates/beauty-salon/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/beauty-salon-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/branded-lite/config.ts +1 -1
- package/templates/branded-lite/dev/index.ts +46 -18
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/branded-lite/tier5-src/_shared/himi-store-collection.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/home-service.ts +2 -1
- package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +48 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-order/index.ts +24 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +24 -3
- package/templates/branded-lite/tier5-src/himi-store-saved/index.ts +8 -3
- package/templates/branded-lite/tier5-src/himi-store-shop/index.ts +10 -0
- package/templates/branded-lite/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/community-club/config.ts +1 -1
- package/templates/community-club/dev/index.ts +3 -1
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/community-club/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/community-club/tier5-src/_shared/home-service.ts +2 -1
- package/templates/community-club/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/community-club/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/community-club-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/events-meetups/config.ts +1 -1
- package/templates/events-meetups/dev/index.ts +3 -1
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/events-meetups/tier5-src/_shared/home-service.ts +2 -1
- package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/events-meetups-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/gym-fitness/config.ts +1 -1
- package/templates/gym-fitness/dev/index.ts +46 -18
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/gym-fitness/tier5-src/_shared/himi-store-collection.ts +1 -1
- package/templates/gym-fitness/tier5-src/_shared/home-service.ts +2 -1
- package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +48 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/gym-fitness/tier5-src/himi-branded-lite-order/index.ts +24 -3
- package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +24 -3
- package/templates/gym-fitness/tier5-src/himi-store-saved/index.ts +8 -3
- package/templates/gym-fitness/tier5-src/himi-store-shop/index.ts +10 -0
- package/templates/gym-fitness/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/gym-fitness-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/nonprofit-cause/config.ts +1 -1
- package/templates/nonprofit-cause/dev/index.ts +29 -13
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/nonprofit-cause/tier5-src/_shared/home-service.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/nonprofit-cause/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/nonprofit-cause-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/owner-lite/tier5-src/_shared/home-service.ts +2 -1
- package/templates/podcast-creator/config.ts +1 -1
- package/templates/podcast-creator/dev/index.ts +29 -13
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/podcast-creator/tier5-src/_shared/home-service.ts +2 -1
- package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/podcast-creator/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/podcast-creator-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/portfolio-creative/config.ts +1 -1
- package/templates/portfolio-creative/dev/index.ts +20 -6
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/portfolio-creative/tier5-src/_shared/himi-store-collection.ts +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/home-service.ts +2 -1
- package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +48 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-order/index.ts +24 -3
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +24 -3
- package/templates/portfolio-creative/tier5-src/himi-store-saved/index.ts +8 -3
- package/templates/portfolio-creative/tier5-src/himi-store-shop/index.ts +10 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/portfolio-creative-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/restaurant/config.ts +1 -1
- package/templates/restaurant/dev/index.ts +3 -1
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/restaurant/tier5-src/_shared/home-service.ts +2 -1
- package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/restaurant/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/restaurant-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/retail-store/config.ts +1 -1
- package/templates/retail-store/dev/index.ts +20 -6
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/retail-store/tier5-src/_shared/himi-store-collection.ts +1 -1
- package/templates/retail-store/tier5-src/_shared/home-service.ts +2 -1
- package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/retail-store/tier5-src/_shared/iap-worker.ts +42 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +48 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/retail-store/tier5-src/himi-branded-lite-order/index.ts +24 -3
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +24 -3
- package/templates/retail-store/tier5-src/himi-store-saved/index.ts +8 -3
- package/templates/retail-store/tier5-src/himi-store-shop/index.ts +10 -0
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/retail-store-owner/tier5-src/_shared/home-service.ts +2 -1
- package/templates/yoga-studio/config.ts +1 -1
- package/templates/yoga-studio/dev/index.ts +29 -13
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/yoga-studio/tier5-src/_shared/home-service.ts +2 -1
- package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +53 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +16 -2
- package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +153 -64
- package/templates/yoga-studio/tier5-src/wix-spaces-plan-detail/index.ts +8 -3
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +1 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +3 -2
- package/templates/yoga-studio-owner/tier5-src/_shared/home-service.ts +2 -1
|
@@ -16,7 +16,7 @@ async function dispatch(handlerId, params) {
|
|
|
16
16
|
return await fn(handlerId, params);
|
|
17
17
|
}
|
|
18
18
|
var DEFAULT_AUTH_PREFIX = "himi.wix";
|
|
19
|
-
var id = (opts,
|
|
19
|
+
var id = (opts, action2) => `${opts?.prefix ?? DEFAULT_AUTH_PREFIX}.${action2}`;
|
|
20
20
|
function emailOf(raw) {
|
|
21
21
|
const direct = raw;
|
|
22
22
|
const wrapped = raw;
|
|
@@ -44,10 +44,10 @@ async function authState(opts) {
|
|
|
44
44
|
return { signedIn: false, email: "" };
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
async function attempt(
|
|
47
|
+
async function attempt(action2, params, failureText, opts) {
|
|
48
48
|
let raw;
|
|
49
49
|
try {
|
|
50
|
-
raw = await dispatch(id(opts,
|
|
50
|
+
raw = await dispatch(id(opts, action2), params);
|
|
51
51
|
} catch (e) {
|
|
52
52
|
return { ok: false, reason: e?.message || failureText };
|
|
53
53
|
}
|
|
@@ -205,59 +205,2800 @@ function createOnlineOrder(planId, couponCode) {
|
|
|
205
205
|
|
|
206
206
|
// ../../stdlib/flows/checkout/src/index.ts
|
|
207
207
|
var GRANTS_ACCESS = "ACTIVE";
|
|
208
|
-
async function entitlement() {
|
|
208
|
+
async function entitlement(planId) {
|
|
209
209
|
try {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
const pages = /* @__PURE__ */ new Set();
|
|
211
|
+
for (let offset = 0; ; offset += 50) {
|
|
212
|
+
const res = await listMyPlanOrders({ statuses: [GRANTS_ACCESS], limit: 50, offset });
|
|
213
|
+
const orders = res.orders ?? [];
|
|
214
|
+
const page = JSON.stringify(orders.map((order) => [order.id, order.planId, order.status]));
|
|
215
|
+
if (orders.length === 50 && pages.has(page)) break;
|
|
216
|
+
pages.add(page);
|
|
217
|
+
const active = orders.find((order) => order.status === GRANTS_ACCESS && (!planId || order.planId === planId));
|
|
218
|
+
if (active) return { entitled: true, planName: active.planName ?? "", planId: active.planId ?? "" };
|
|
219
|
+
if (orders.length < 50) break;
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
return { entitled: false, planName: "", planId: "" };
|
|
224
|
+
}
|
|
225
|
+
var WITHDRAWN = { kind: "failed", reason: "the caller withdrew the charge" };
|
|
226
|
+
async function payOrder(req) {
|
|
227
|
+
if (!req.snapshotId && !req.cashierUrl) {
|
|
228
|
+
return { kind: "failed", reason: "no order to settle" };
|
|
229
|
+
}
|
|
230
|
+
const availability = await isAvailable().catch(() => "unavailable");
|
|
231
|
+
if (availability === "full" && req.snapshotId) {
|
|
232
|
+
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
|
233
|
+
const r = await proceedToPayment({
|
|
234
|
+
appId: req.appId,
|
|
235
|
+
snapshotId: req.snapshotId,
|
|
236
|
+
msid: req.msid,
|
|
237
|
+
amount: req.amount,
|
|
238
|
+
currency: req.currency
|
|
239
|
+
}).catch(() => ({ status: "failed" }));
|
|
240
|
+
if (r.status === "paid" || r.status === "pending") {
|
|
241
|
+
const chargeId = r.chargeId;
|
|
242
|
+
return {
|
|
243
|
+
kind: "paid",
|
|
244
|
+
pending: r.status === "pending",
|
|
245
|
+
...typeof chargeId === "string" ? { chargeId } : {}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (r.status === "cancelled") return { kind: "cancelled" };
|
|
249
|
+
}
|
|
250
|
+
if (req.cashierUrl) {
|
|
251
|
+
const url = await req.cashierUrl().catch(() => void 0);
|
|
252
|
+
if (url) {
|
|
253
|
+
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
|
254
|
+
const opened = await open(url, "in_app_browser").catch(() => ({ opened: false }));
|
|
255
|
+
if (opened.opened) return { kind: "handoff" };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
kind: "failed",
|
|
260
|
+
reason: availability === "full" ? "the wallet declined and no in-app cashier is configured" : "no payment rail is available"
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ../../core/sdk/app-info/src/index.ts
|
|
265
|
+
var PLATFORMS = ["ios", "android", "macos", "web", "react-native"];
|
|
266
|
+
function str(v) {
|
|
267
|
+
return typeof v === "string" ? v.trim() : "";
|
|
268
|
+
}
|
|
269
|
+
async function get() {
|
|
270
|
+
const r = await dispatch2("capability.app_info.get");
|
|
271
|
+
const platform = str(r?.platform);
|
|
272
|
+
const releaseId = str(r?.releaseId);
|
|
273
|
+
const serveBaseUrl = str(r?.serveBaseUrl);
|
|
274
|
+
return {
|
|
275
|
+
appId: str(r?.appId),
|
|
276
|
+
bundleId: str(r?.bundleId),
|
|
277
|
+
version: str(r?.version),
|
|
278
|
+
buildNumber: str(r?.buildNumber),
|
|
279
|
+
binaryVersion: str(r?.binaryVersion),
|
|
280
|
+
releaseId: releaseId || null,
|
|
281
|
+
releasePinned: r?.releasePinned === true,
|
|
282
|
+
// Trust the host's own answer; fall back to inferring from whether a serve origin is set.
|
|
283
|
+
environment: str(r?.environment) === "prod" ? "prod" : serveBaseUrl ? "prod" : "dev",
|
|
284
|
+
serveBaseUrl: serveBaseUrl || null,
|
|
285
|
+
platform: PLATFORMS.includes(platform) ? platform : "unknown"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ../../core/sdk/iap/src/delivery.ts
|
|
290
|
+
var MAX_AUTOMATIC_ATTEMPTS = 5;
|
|
291
|
+
var MAX_RETRY_DELAY_MS = 6e4;
|
|
292
|
+
var BASE_RETRY_DELAY_MS = 1e3;
|
|
293
|
+
var UNAVAILABLE_CODES = /* @__PURE__ */ new Set(["unsupported", "unavailable", "notEnabled"]);
|
|
294
|
+
function createPurchaseCoordinator(deps) {
|
|
295
|
+
const items = /* @__PURE__ */ new Map();
|
|
296
|
+
const queue = createSerialQueue();
|
|
297
|
+
const store = guardStore(deps.store);
|
|
298
|
+
let outcome = "idle";
|
|
299
|
+
let purchasing = false;
|
|
300
|
+
let signInPrompt = false;
|
|
301
|
+
let unavailable2 = null;
|
|
302
|
+
let pendingApproval = null;
|
|
303
|
+
let errorCode = null;
|
|
304
|
+
let errorFromReconcile = false;
|
|
305
|
+
let unfinishedCount = null;
|
|
306
|
+
let generation = deps.session.current().generation;
|
|
307
|
+
let staleSignal = createSignal();
|
|
308
|
+
let started = false;
|
|
309
|
+
let disposed = false;
|
|
310
|
+
const unsubscribe = deps.session.subscribe(onSessionNotified);
|
|
311
|
+
function start() {
|
|
312
|
+
started = true;
|
|
313
|
+
return reconcile2();
|
|
314
|
+
}
|
|
315
|
+
function purchase(catalogRef) {
|
|
316
|
+
onSessionNotified();
|
|
317
|
+
const requestedBy = deps.session.current();
|
|
318
|
+
const name = `purchase:${requestedBy.generation}:${catalogRef}`;
|
|
319
|
+
return runJob(name, () => runPurchase(requestedBy, catalogRef), { joinRunning: true }).then(([dropped, state]) => dropped ? { ...state, errorCode: "sessionChanged" } : state);
|
|
320
|
+
}
|
|
321
|
+
function reconcile2() {
|
|
322
|
+
resetIfSessionChanged();
|
|
323
|
+
return enqueue("reconcile", runReconcile);
|
|
324
|
+
}
|
|
325
|
+
function retry2() {
|
|
326
|
+
resetIfSessionChanged();
|
|
327
|
+
return enqueue("retry", runRetry);
|
|
328
|
+
}
|
|
329
|
+
function tick() {
|
|
330
|
+
if (resetIfSessionChanged() && started) return reconcile2();
|
|
331
|
+
return enqueue("tick", runDue);
|
|
332
|
+
}
|
|
333
|
+
function getState() {
|
|
334
|
+
if (disposed || !sessionMatches()) return { phase: "idle", errorCode: null, items: [], unfinishedCount: null, nextWakeAt: null };
|
|
335
|
+
const snapshot = [...items.values()].map((item) => ({ ...item.state }));
|
|
336
|
+
const due = snapshot.map((item) => item.nextAttemptAt).filter((at) => at !== null);
|
|
337
|
+
return { phase: currentPhase(), errorCode: errorCode ?? unavailable2?.code ?? null, items: snapshot, unfinishedCount, nextWakeAt: due.length > 0 ? Math.min(...due) : null };
|
|
338
|
+
}
|
|
339
|
+
function dispose() {
|
|
340
|
+
disposed = true;
|
|
341
|
+
unsubscribe();
|
|
342
|
+
staleSignal.fire();
|
|
343
|
+
}
|
|
344
|
+
async function runPurchase(session, catalogRef) {
|
|
345
|
+
if (!isCurrent(session)) return !disposed;
|
|
346
|
+
setError(null, false);
|
|
347
|
+
outcome = "idle";
|
|
348
|
+
pendingApproval = null;
|
|
349
|
+
unavailable2 = null;
|
|
350
|
+
releaseSignIn();
|
|
351
|
+
if (session.kind !== "member") {
|
|
352
|
+
requireSignIn();
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
const context = await purchaseContext(session, catalogRef);
|
|
356
|
+
if (!isCurrent(session)) return !disposed;
|
|
357
|
+
if (!context) return false;
|
|
358
|
+
purchasing = true;
|
|
359
|
+
publish();
|
|
360
|
+
const { productId, appAccountToken } = context;
|
|
361
|
+
const result = await store.purchaseForVerification({ productId, appAccountToken });
|
|
362
|
+
purchasing = false;
|
|
363
|
+
if (!isCurrent(session)) return !disposed;
|
|
364
|
+
if (result.status === "purchased") await deliverPurchase(session, result.transaction, context);
|
|
365
|
+
else if (result.status === "failed") failStore(result.code, false);
|
|
366
|
+
else if (result.status === "pending") {
|
|
367
|
+
if (!await awaitApproval(session, context)) return !disposed;
|
|
368
|
+
} else setOutcome("cancelled");
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
async function deliverPurchase(session, transaction, context) {
|
|
372
|
+
const item = upsert2(transaction, null);
|
|
373
|
+
applyDelivered(await deliver(session, [item], context));
|
|
374
|
+
await enumerate(session);
|
|
375
|
+
publish();
|
|
376
|
+
}
|
|
377
|
+
async function runReconcile() {
|
|
378
|
+
resetIfSessionChanged();
|
|
379
|
+
const session = deps.session.current();
|
|
380
|
+
const listing = await enumerate(session);
|
|
381
|
+
if (listing === "stale") return;
|
|
382
|
+
if (session.kind !== "member") return requireSignIn();
|
|
383
|
+
if (listing === "listed") clearReconcileProblem();
|
|
384
|
+
const now = deps.clock.now();
|
|
385
|
+
const eligible = itemsWhere((item) => isAwaitingDelivery(item) && (item.state.nextAttemptAt === null || item.state.nextAttemptAt <= now));
|
|
386
|
+
eligible.forEach(restartAttemptBudget);
|
|
387
|
+
await settleDelivery(session, await deliver(session, eligible));
|
|
388
|
+
}
|
|
389
|
+
async function settleDelivery(session, delivered) {
|
|
390
|
+
applyDelivered(delivered);
|
|
391
|
+
if (delivered.finished) await enumerate(session);
|
|
392
|
+
publish();
|
|
393
|
+
}
|
|
394
|
+
function applyDelivered(delivered) {
|
|
395
|
+
if (delivered.confirmedProducts.length === 0) return;
|
|
396
|
+
if (outcome === "cancelled") outcome = "idle";
|
|
397
|
+
if (!delivered.signInLost) releaseSignIn();
|
|
398
|
+
}
|
|
399
|
+
function releaseSignIn() {
|
|
400
|
+
if (!signInPrompt) return;
|
|
401
|
+
signInPrompt = false;
|
|
402
|
+
const now = deps.clock.now();
|
|
403
|
+
for (const item of items.values()) {
|
|
404
|
+
if (item.state.access === "signInRequired") Object.assign(item.state, { access: "waitingToRetry", attempts: 0, nextAttemptAt: now });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function clearReconcileProblem() {
|
|
408
|
+
if (unavailable2?.from === "reconcile") unavailable2 = null;
|
|
409
|
+
if (errorFromReconcile) setError(null, false);
|
|
410
|
+
}
|
|
411
|
+
async function runRetry() {
|
|
412
|
+
releaseSignIn();
|
|
413
|
+
for (const item of items.values()) unblock(item);
|
|
414
|
+
await runReconcile();
|
|
415
|
+
}
|
|
416
|
+
async function runDue() {
|
|
417
|
+
if (resetIfSessionChanged()) return started ? runReconcile() : void 0;
|
|
418
|
+
const session = memberSession();
|
|
419
|
+
const now = deps.clock.now();
|
|
420
|
+
const due = itemsWhere((item) => item.state.nextAttemptAt !== null && item.state.nextAttemptAt <= now);
|
|
421
|
+
if (!session || due.length === 0) return;
|
|
422
|
+
await settleDelivery(session, await deliver(session, due));
|
|
423
|
+
}
|
|
424
|
+
async function deliver(session, batch, known) {
|
|
425
|
+
const needProof = batch.filter((item) => !item.finishAllowed);
|
|
426
|
+
const context = known ?? (needProof.length > 0 ? await memberContext(session, needProof) : void 0);
|
|
427
|
+
const delivered = noDelivery();
|
|
428
|
+
for (const item of batch) {
|
|
429
|
+
if (!isCurrent(session)) return noDelivery();
|
|
430
|
+
const wasAllowed = item.finishAllowed;
|
|
431
|
+
if (delivered.signInLost && !wasAllowed) {
|
|
432
|
+
blockWithoutSession(item);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (await deliverItem(session, item, context)) delivered.finished = true;
|
|
436
|
+
if (!wasAllowed && item.finishAllowed) delivered.confirmedProducts.push(item.state.productId);
|
|
437
|
+
if (item.state.access === "signInRequired") delivered.signInLost = true;
|
|
438
|
+
}
|
|
439
|
+
return delivered;
|
|
440
|
+
}
|
|
441
|
+
function blockWithoutSession(item) {
|
|
442
|
+
block(item.state, item.transaction.appAccountToken === null ? "ownershipConflict" : "signInRequired", null);
|
|
443
|
+
}
|
|
444
|
+
async function deliverItem(session, item, context) {
|
|
445
|
+
if (!item.finishAllowed) {
|
|
446
|
+
if (!context || !await confirmAccess(session, item, context)) return false;
|
|
447
|
+
}
|
|
448
|
+
return complete(session, item);
|
|
449
|
+
}
|
|
450
|
+
async function confirmAccess(session, item, context) {
|
|
451
|
+
if (item.transaction.appAccountToken === null) {
|
|
452
|
+
block(item.state, "ownershipConflict", null);
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
updateItem(item, { access: "confirming", nextAttemptAt: null });
|
|
456
|
+
const settled = await settle(session, () => deps.backend.submitProof(session, item.transaction));
|
|
457
|
+
if (settled.kind === "stale") return false;
|
|
458
|
+
if (settled.kind === "error") return recordFailedAttempt(item, void 0);
|
|
459
|
+
return applySubmission(item, settled.value, context.scopeKey);
|
|
460
|
+
}
|
|
461
|
+
function applySubmission(item, raw, scopeKey) {
|
|
462
|
+
if (!isObject(raw)) return block(item.state, "rejected", "invalidResponse");
|
|
463
|
+
switch (raw.status) {
|
|
464
|
+
case "processed":
|
|
465
|
+
case "alreadyProcessed":
|
|
466
|
+
case "accepted":
|
|
467
|
+
case "refusedFinal":
|
|
468
|
+
if (!acknowledges(raw.acknowledgement, item.transaction, scopeKey)) return block(item.state, "rejected", "invalidResponse");
|
|
469
|
+
item.finishAllowed = true;
|
|
470
|
+
Object.assign(item.state, { ...FINISH_ALLOWED_STATE[raw.status], nextAttemptAt: null });
|
|
471
|
+
return true;
|
|
472
|
+
case "retryable":
|
|
473
|
+
return recordFailedAttempt(item, retryAfter(raw.retryAfterMs));
|
|
474
|
+
case "authRequired":
|
|
475
|
+
signInPrompt = true;
|
|
476
|
+
return block(item.state, "signInRequired", null);
|
|
477
|
+
case "ownershipConflict":
|
|
478
|
+
return block(item.state, "ownershipConflict", null);
|
|
479
|
+
case "rejected":
|
|
480
|
+
return block(item.state, "rejected", "proofRejected");
|
|
481
|
+
default:
|
|
482
|
+
return block(item.state, "rejected", "invalidResponse");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function complete(session, item) {
|
|
486
|
+
if (!isCurrent(session)) return false;
|
|
487
|
+
const result = await store.finishTransaction({ transactionId: item.transaction.transactionId });
|
|
488
|
+
if (!isCurrent(session)) return false;
|
|
489
|
+
if (result.status === "failed") {
|
|
490
|
+
item.state.completion = "retryRequired";
|
|
491
|
+
recordFailedAttempt(item, void 0);
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
Object.assign(item.state, { completion: result.status, held: false, nextAttemptAt: null });
|
|
495
|
+
return result.status === "finished";
|
|
496
|
+
}
|
|
497
|
+
function recordFailedAttempt(item, retryAfterMs) {
|
|
498
|
+
const state = item.state;
|
|
499
|
+
state.attempts += 1;
|
|
500
|
+
if (!item.finishAllowed) state.access = "waitingToRetry";
|
|
501
|
+
state.nextAttemptAt = state.attempts >= MAX_AUTOMATIC_ATTEMPTS ? null : deps.clock.now() + retryDelay(state.attempts, retryAfterMs);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
function retryDelay(attempts, retryAfterMs) {
|
|
505
|
+
if (retryAfterMs !== void 0) return Math.min(retryAfterMs, MAX_RETRY_DELAY_MS);
|
|
506
|
+
const base = Math.min(BASE_RETRY_DELAY_MS * 2 ** (attempts - 1), MAX_RETRY_DELAY_MS);
|
|
507
|
+
return base + Math.max(0, deps.jitter?.(base) ?? 0);
|
|
508
|
+
}
|
|
509
|
+
async function purchaseContext(session, catalogRef) {
|
|
510
|
+
const settled = await settle(session, () => deps.backend.getPurchaseContext(session, catalogRef));
|
|
511
|
+
if (settled.kind === "stale") return null;
|
|
512
|
+
const decoded = decodeContext(settled, isPurchaseContext);
|
|
513
|
+
if (decoded.kind === "ok") return decoded.context;
|
|
514
|
+
if (decoded.kind === "signInRequired") requireSignIn();
|
|
515
|
+
else failContext(decoded.kind);
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
async function memberContext(session, waiting) {
|
|
519
|
+
const settled = await settle(session, () => deps.backend.getMemberContext(session));
|
|
520
|
+
if (settled.kind === "stale") return void 0;
|
|
521
|
+
const decoded = decodeContext(settled, isMemberContext);
|
|
522
|
+
if (decoded.kind === "ok") return decoded.context;
|
|
523
|
+
if (decoded.kind === "rejected") setError("contextRejected", true);
|
|
524
|
+
if (decoded.kind === "signInRequired") signInPrompt = true;
|
|
525
|
+
for (const item of waiting) {
|
|
526
|
+
if (decoded.kind === "unavailable") recordFailedAttempt(item, decoded.retryAfterMs);
|
|
527
|
+
else if (decoded.kind === "rejected") block(item.state, "waitingToRetry", "contextRejected");
|
|
528
|
+
else block(item.state, "signInRequired", null);
|
|
529
|
+
}
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
532
|
+
async function enumerate(session, { quiet = false } = {}) {
|
|
533
|
+
if (!isCurrent(session)) return "stale";
|
|
534
|
+
const result = await store.getUnfinishedTransactions();
|
|
535
|
+
if (!isCurrent(session)) return "stale";
|
|
536
|
+
if (result.status !== "ok") {
|
|
537
|
+
unfinishedCount = null;
|
|
538
|
+
if (!quiet) failStore(result.code, true);
|
|
539
|
+
return "failed";
|
|
540
|
+
}
|
|
541
|
+
const held = new Set(result.transactions.map((transaction) => upsert2(transaction, true)));
|
|
542
|
+
for (const item of items.values()) if (!held.has(item)) item.state.held = false;
|
|
543
|
+
unfinishedCount = result.transactions.length;
|
|
544
|
+
return "listed";
|
|
545
|
+
}
|
|
546
|
+
function upsert2(transaction, held) {
|
|
547
|
+
const key = identityOf(transaction);
|
|
548
|
+
const existing = items.get(key);
|
|
549
|
+
if (existing) {
|
|
550
|
+
existing.transaction = transaction;
|
|
551
|
+
if (held !== null) existing.state.held = held;
|
|
552
|
+
return existing;
|
|
553
|
+
}
|
|
554
|
+
if (pendingApproval?.productId === transaction.productId && !pendingApproval.known.has(key) && sameAccount(transaction.appAccountToken, pendingApproval.token)) {
|
|
555
|
+
pendingApproval = null;
|
|
556
|
+
if (outcome === "awaitingApproval") outcome = "idle";
|
|
557
|
+
}
|
|
558
|
+
const item = { transaction, state: newItemState(transaction, held), finishAllowed: false };
|
|
559
|
+
items.set(key, item);
|
|
560
|
+
return item;
|
|
561
|
+
}
|
|
562
|
+
function onSessionNotified() {
|
|
563
|
+
if (resetIfSessionChanged() && started) void reconcile2();
|
|
564
|
+
}
|
|
565
|
+
function resetIfSessionChanged() {
|
|
566
|
+
const next = deps.session.current().generation;
|
|
567
|
+
if (disposed || next === generation) return false;
|
|
568
|
+
generation = next;
|
|
569
|
+
staleSignal.fire();
|
|
570
|
+
staleSignal = createSignal();
|
|
571
|
+
items.clear();
|
|
572
|
+
outcome = "idle";
|
|
573
|
+
purchasing = false;
|
|
574
|
+
signInPrompt = false;
|
|
575
|
+
unavailable2 = null;
|
|
576
|
+
pendingApproval = null;
|
|
577
|
+
unfinishedCount = null;
|
|
578
|
+
setError(null, false);
|
|
579
|
+
publish();
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
function memberSession() {
|
|
583
|
+
const session = deps.session.current();
|
|
584
|
+
return session.kind === "member" ? session : null;
|
|
585
|
+
}
|
|
586
|
+
function sessionMatches() {
|
|
587
|
+
return deps.session.current().generation === generation;
|
|
588
|
+
}
|
|
589
|
+
function isCurrent(session) {
|
|
590
|
+
return !disposed && session.generation === generation && sessionMatches();
|
|
591
|
+
}
|
|
592
|
+
async function settle(session, call) {
|
|
593
|
+
const stale = staleSignal.waiter();
|
|
594
|
+
try {
|
|
595
|
+
const pending = Promise.resolve().then(() => {
|
|
596
|
+
if (!isCurrent(session)) throw ABANDONED;
|
|
597
|
+
return call();
|
|
598
|
+
});
|
|
599
|
+
const settled = await Promise.race([pending.then((value) => ({ kind: "value", value })), stale.fired.then(() => STALE)]);
|
|
600
|
+
return isCurrent(session) ? settled : STALE;
|
|
601
|
+
} catch {
|
|
602
|
+
return isCurrent(session) ? { kind: "error" } : STALE;
|
|
603
|
+
} finally {
|
|
604
|
+
stale.cancel();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function runJob(name, run, options = {}) {
|
|
608
|
+
return queue.run(name, () => disposed ? Promise.resolve(void 0) : run(), options).then((result) => [result, getState()]);
|
|
609
|
+
}
|
|
610
|
+
function enqueue(name, run) {
|
|
611
|
+
return runJob(name, run).then(([, state]) => state);
|
|
612
|
+
}
|
|
613
|
+
function itemsWhere(predicate) {
|
|
614
|
+
return [...items.values()].filter(predicate);
|
|
615
|
+
}
|
|
616
|
+
function updateItem(item, patch) {
|
|
617
|
+
Object.assign(item.state, patch);
|
|
618
|
+
publish();
|
|
619
|
+
}
|
|
620
|
+
function requireSignIn() {
|
|
621
|
+
for (const item of items.values()) {
|
|
622
|
+
if (isAwaitingDelivery(item) && !item.finishAllowed) block(item.state, "signInRequired", null);
|
|
623
|
+
}
|
|
624
|
+
signInPrompt = true;
|
|
625
|
+
publish();
|
|
626
|
+
}
|
|
627
|
+
function failContext(reason) {
|
|
628
|
+
setError(reason === "rejected" ? "contextRejected" : "contextUnavailable", false);
|
|
629
|
+
publish();
|
|
630
|
+
}
|
|
631
|
+
function failStore(code, fromReconcile) {
|
|
632
|
+
setError(code, fromReconcile);
|
|
633
|
+
if (UNAVAILABLE_CODES.has(code) && unavailable2?.from !== "purchase") {
|
|
634
|
+
unavailable2 = { from: fromReconcile ? "reconcile" : "purchase", code };
|
|
635
|
+
}
|
|
636
|
+
publish();
|
|
637
|
+
}
|
|
638
|
+
async function awaitApproval(session, context) {
|
|
639
|
+
if (await enumerate(session, { quiet: true }) === "stale") return false;
|
|
640
|
+
const known = new Set([...items.keys()].filter((key) => items.get(key).state.productId === context.productId));
|
|
641
|
+
pendingApproval = { productId: context.productId, token: context.appAccountToken, known };
|
|
642
|
+
setOutcome("awaitingApproval");
|
|
643
|
+
deps.onAwaitingApproval?.(context.productId);
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
function setOutcome(next) {
|
|
647
|
+
outcome = next;
|
|
648
|
+
publish();
|
|
649
|
+
}
|
|
650
|
+
function currentPhase() {
|
|
651
|
+
if (purchasing) return "purchasing";
|
|
652
|
+
if (unavailable2?.from === "purchase") return "unavailable";
|
|
653
|
+
if (signInPrompt) return "signInRequired";
|
|
654
|
+
if (unavailable2?.from === "reconcile") return "unavailable";
|
|
655
|
+
return outcome;
|
|
656
|
+
}
|
|
657
|
+
function setError(code, fromReconcile) {
|
|
658
|
+
errorCode = code;
|
|
659
|
+
errorFromReconcile = code !== null && fromReconcile;
|
|
660
|
+
}
|
|
661
|
+
function publish() {
|
|
662
|
+
if (!disposed && sessionMatches()) deps.onState?.(getState());
|
|
663
|
+
}
|
|
664
|
+
return { start, purchase, reconcile: reconcile2, retry: retry2, tick, getState, dispose };
|
|
665
|
+
}
|
|
666
|
+
var FINISH_ALLOWED_STATE = {
|
|
667
|
+
processed: { access: "confirmed", errorCode: null },
|
|
668
|
+
alreadyProcessed: { access: "confirmed", errorCode: null },
|
|
669
|
+
accepted: { access: "processing", errorCode: null },
|
|
670
|
+
refusedFinal: { access: "rejected", errorCode: "proofRejected" }
|
|
671
|
+
};
|
|
672
|
+
var noDelivery = () => ({ finished: false, confirmedProducts: [], signInLost: false });
|
|
673
|
+
var STALE = { kind: "stale" };
|
|
674
|
+
var ABANDONED = { abandoned: true };
|
|
675
|
+
async function storeCall(call) {
|
|
676
|
+
try {
|
|
677
|
+
return await call();
|
|
678
|
+
} catch {
|
|
679
|
+
return { status: "failed", code: "storeError" };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function guardStore(store) {
|
|
683
|
+
return {
|
|
684
|
+
purchaseForVerification: (input) => storeCall(() => store.purchaseForVerification(input)),
|
|
685
|
+
getUnfinishedTransactions: () => storeCall(() => store.getUnfinishedTransactions()),
|
|
686
|
+
finishTransaction: (input) => storeCall(() => store.finishTransaction(input))
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function identityOf(transaction) {
|
|
690
|
+
return `${transaction.store}:${transaction.environment}:${transaction.transactionId}`;
|
|
691
|
+
}
|
|
692
|
+
function newItemState(transaction, held) {
|
|
693
|
+
return {
|
|
694
|
+
productId: transaction.productId,
|
|
695
|
+
environment: transaction.environment,
|
|
696
|
+
held,
|
|
697
|
+
access: "pending",
|
|
698
|
+
completion: "pending",
|
|
699
|
+
attempts: 0,
|
|
700
|
+
nextAttemptAt: null,
|
|
701
|
+
errorCode: null
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function isAwaitingDelivery(item) {
|
|
705
|
+
const state = item.state;
|
|
706
|
+
if (item.finishAllowed) return state.completion === "pending" || state.completion === "retryRequired";
|
|
707
|
+
return state.access === "pending" || state.access === "waitingToRetry" || state.access === "signInRequired";
|
|
708
|
+
}
|
|
709
|
+
function restartAttemptBudget(item) {
|
|
710
|
+
item.state.attempts = 0;
|
|
711
|
+
}
|
|
712
|
+
function unblock(item) {
|
|
713
|
+
if (!item.finishAllowed) Object.assign(item.state, { access: "pending", errorCode: null });
|
|
714
|
+
Object.assign(item.state, { attempts: 0, nextAttemptAt: null });
|
|
715
|
+
}
|
|
716
|
+
function block(state, access, errorCode) {
|
|
717
|
+
Object.assign(state, { access, errorCode, nextAttemptAt: null });
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
function sameAccount(transactionToken, memberToken) {
|
|
721
|
+
return transactionToken !== null && transactionToken.toLowerCase() === memberToken.toLowerCase();
|
|
722
|
+
}
|
|
723
|
+
function acknowledges(acknowledgement, transaction, scopeKey) {
|
|
724
|
+
return isObject(acknowledgement) && acknowledgement.transactionId === transaction.transactionId && acknowledgement.productId === transaction.productId && acknowledgement.environment === transaction.environment && acknowledgement.scopeKey === scopeKey;
|
|
725
|
+
}
|
|
726
|
+
function decodeContext(settled, isContext) {
|
|
727
|
+
const raw = settled.kind === "value" ? settled.value : null;
|
|
728
|
+
if (!isObject(raw)) return { kind: "unavailable" };
|
|
729
|
+
if (raw.status === "ok" && isContext(raw.context)) return { kind: "ok", context: raw.context };
|
|
730
|
+
if (raw.status === "authRequired") return { kind: "signInRequired" };
|
|
731
|
+
if (raw.status === "rejected") return { kind: "rejected" };
|
|
732
|
+
return { kind: "unavailable", retryAfterMs: retryAfter(raw.retryAfterMs) };
|
|
733
|
+
}
|
|
734
|
+
function isPurchaseContext(value) {
|
|
735
|
+
return isMemberContext(value) && isObject(value) && isNonEmpty(value.productId) && isNonEmpty(value.appAccountToken);
|
|
736
|
+
}
|
|
737
|
+
function isMemberContext(value) {
|
|
738
|
+
return isObject(value) && isNonEmpty(value.scopeKey);
|
|
739
|
+
}
|
|
740
|
+
function retryAfter(value) {
|
|
741
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
742
|
+
}
|
|
743
|
+
function isNonEmpty(value) {
|
|
744
|
+
return typeof value === "string" && value.length > 0;
|
|
745
|
+
}
|
|
746
|
+
function isObject(value) {
|
|
747
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
748
|
+
}
|
|
749
|
+
function createSignal() {
|
|
750
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
751
|
+
let hasFired = false;
|
|
752
|
+
return {
|
|
753
|
+
fire() {
|
|
754
|
+
hasFired = true;
|
|
755
|
+
for (const listener of listeners) listener();
|
|
756
|
+
listeners.clear();
|
|
757
|
+
},
|
|
758
|
+
waiter() {
|
|
759
|
+
let listener = () => {
|
|
760
|
+
};
|
|
761
|
+
const fired = new Promise((resolve) => {
|
|
762
|
+
listener = resolve;
|
|
763
|
+
});
|
|
764
|
+
if (hasFired) listener();
|
|
765
|
+
else listeners.add(listener);
|
|
766
|
+
return { fired, cancel: () => {
|
|
767
|
+
listeners.delete(listener);
|
|
768
|
+
} };
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function createSerialQueue() {
|
|
773
|
+
let tail = Promise.resolve();
|
|
774
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
775
|
+
const running = /* @__PURE__ */ new Map();
|
|
776
|
+
async function execute(name, self, job) {
|
|
777
|
+
waiting.delete(name);
|
|
778
|
+
running.set(name, self);
|
|
779
|
+
try {
|
|
780
|
+
return await job();
|
|
781
|
+
} catch {
|
|
782
|
+
return void 0;
|
|
783
|
+
} finally {
|
|
784
|
+
running.delete(name);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
/** Callers joined onto a shared job all receive that job's result. */
|
|
789
|
+
run(name, job, { joinRunning = false }) {
|
|
790
|
+
const shared2 = waiting.get(name) ?? (joinRunning ? running.get(name) : void 0);
|
|
791
|
+
if (shared2) return shared2;
|
|
792
|
+
const scheduled = tail.then(() => execute(name, scheduled, job));
|
|
793
|
+
waiting.set(name, scheduled);
|
|
794
|
+
tail = scheduled;
|
|
795
|
+
return scheduled;
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ../../core/sdk/iap/src/pricing.ts
|
|
801
|
+
function createPricingController(deps) {
|
|
802
|
+
let state = { revision: 0, scope: null, items: [] };
|
|
803
|
+
let lastInput;
|
|
804
|
+
let active;
|
|
805
|
+
let disposed = false;
|
|
806
|
+
function load(input) {
|
|
807
|
+
if (disposed) return Promise.resolve();
|
|
808
|
+
const captured = captureInput(input);
|
|
809
|
+
const key = JSON.stringify(captured);
|
|
810
|
+
if (active?.key === key) return active.promise;
|
|
811
|
+
lastInput = captured;
|
|
812
|
+
const revision = state.revision + 1;
|
|
813
|
+
let complete;
|
|
814
|
+
const promise = new Promise((resolve) => {
|
|
815
|
+
complete = resolve;
|
|
816
|
+
});
|
|
817
|
+
active = { key, promise };
|
|
818
|
+
publish({ revision, scope: captured.scope, items: captured.items.map((item) => ({ reference: item.reference, status: "loading" })) });
|
|
819
|
+
void run(captured, revision).finally(complete);
|
|
820
|
+
return promise;
|
|
821
|
+
}
|
|
822
|
+
async function run(input, revision) {
|
|
823
|
+
const current = () => !disposed && state.revision === revision;
|
|
824
|
+
if (!current()) return;
|
|
825
|
+
const items = await resolvePrices(input, deps, current);
|
|
826
|
+
if (!current()) return;
|
|
827
|
+
active = void 0;
|
|
828
|
+
publish({ revision, scope: input.scope, items });
|
|
829
|
+
}
|
|
830
|
+
function publish(next) {
|
|
831
|
+
state = next;
|
|
832
|
+
try {
|
|
833
|
+
deps.onState?.(getState());
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function getState() {
|
|
838
|
+
return {
|
|
839
|
+
...state,
|
|
840
|
+
scope: state.scope ? { ...state.scope } : null,
|
|
841
|
+
items: state.items.map((item) => ({ ...item, reference: { ...item.reference } }))
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function dispose() {
|
|
845
|
+
disposed = true;
|
|
846
|
+
active = void 0;
|
|
847
|
+
lastInput = void 0;
|
|
848
|
+
state = { revision: state.revision + 1, scope: null, items: [] };
|
|
849
|
+
}
|
|
850
|
+
return { load, refresh: () => lastInput ? load(lastInput) : Promise.resolve(), getState, dispose };
|
|
851
|
+
}
|
|
852
|
+
function captureInput(input) {
|
|
853
|
+
const scope = {
|
|
854
|
+
appId: input.scope.appId,
|
|
855
|
+
bundleId: input.scope.bundleId,
|
|
856
|
+
siteId: input.scope.siteId,
|
|
857
|
+
...input.scope.contextRevision === void 0 ? {} : { contextRevision: input.scope.contextRevision }
|
|
858
|
+
};
|
|
859
|
+
const items = /* @__PURE__ */ new Map();
|
|
860
|
+
for (const item of input.items) {
|
|
861
|
+
const reference = { kind: item.reference.kind, id: item.reference.id };
|
|
862
|
+
const key = JSON.stringify([reference.kind, reference.id]);
|
|
863
|
+
if (!items.has(key)) items.set(key, { reference, ...item.commerceDisplayPrice === void 0 ? {} : { commerceDisplayPrice: item.commerceDisplayPrice } });
|
|
864
|
+
}
|
|
865
|
+
return { scope, platform: input.platform, items: [...items.values()] };
|
|
866
|
+
}
|
|
867
|
+
async function resolvePrices(input, deps, current) {
|
|
868
|
+
if (input.items.length === 0) return [];
|
|
869
|
+
if (![input.scope.appId, input.scope.bundleId, input.scope.siteId].every(nonempty)) {
|
|
870
|
+
return input.items.map((item) => unavailable(item.reference, "invalidInput"));
|
|
871
|
+
}
|
|
872
|
+
const mappings = await resolveMappings(input, deps);
|
|
873
|
+
if (!current()) return [];
|
|
874
|
+
if (mappings === null) return input.items.map((item) => unavailable(item.reference, "mappingUnavailable"));
|
|
875
|
+
const routes = input.items.map((item) => resolveRoute(item, mappings, input.platform));
|
|
876
|
+
const ids = [...new Set(routes.flatMap((route) => route.status === "loading" ? [route.mapping.productId] : []))];
|
|
877
|
+
const products = ids.length ? await lookupProducts(ids, deps) : [];
|
|
878
|
+
return routes.map((route) => route.status === "loading" ? applePrice(route.mapping, products) : route);
|
|
879
|
+
}
|
|
880
|
+
async function resolveMappings(input, deps) {
|
|
881
|
+
try {
|
|
882
|
+
const references = input.items.filter((item) => nonempty(item.reference.kind) && nonempty(item.reference.id)).map((item) => ({ ...item.reference }));
|
|
883
|
+
if (!references.length) return [];
|
|
884
|
+
const result = await deps.mapping.resolve({ scope: { ...input.scope }, references });
|
|
885
|
+
return Array.isArray(result) ? result : [];
|
|
886
|
+
} catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async function lookupProducts(ids, deps) {
|
|
891
|
+
try {
|
|
892
|
+
const products = await deps.getProducts(ids);
|
|
893
|
+
return Array.isArray(products) ? products : [];
|
|
894
|
+
} catch {
|
|
895
|
+
return [];
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function resolveRoute(item, mappings, platform) {
|
|
899
|
+
if (!nonempty(item.reference.kind) || !nonempty(item.reference.id)) return unavailable(item.reference, "invalidInput");
|
|
900
|
+
const matches = mappings.filter((value) => isObject2(value) && sameReference(value.reference, item.reference));
|
|
901
|
+
if (matches.length !== 1) return unavailable(item.reference, "mappingUnresolved");
|
|
902
|
+
const mapping = matches[0];
|
|
903
|
+
if (mapping.route === "regular") return regularPrice(item);
|
|
904
|
+
if (mapping.route !== "appleIap" || !nonempty(mapping.productId) || mapping.productId.includes(",") || !nonempty(mapping.mappingRevision)) {
|
|
905
|
+
return unavailable(item.reference, "mappingUnresolved");
|
|
906
|
+
}
|
|
907
|
+
if (platform !== "ios") return unavailable(item.reference, "unsupportedPlatform");
|
|
908
|
+
return { status: "loading", mapping: { reference: item.reference, route: "appleIap", productId: mapping.productId, mappingRevision: mapping.mappingRevision } };
|
|
909
|
+
}
|
|
910
|
+
function regularPrice(item) {
|
|
911
|
+
return nonempty(item.commerceDisplayPrice) ? { reference: item.reference, status: "ready", source: "regular", displayPrice: item.commerceDisplayPrice } : unavailable(item.reference, "commercePriceUnavailable");
|
|
912
|
+
}
|
|
913
|
+
function applePrice(mapping, products) {
|
|
914
|
+
const matches = products.filter((value) => isObject2(value) && value.id === mapping.productId);
|
|
915
|
+
const product = matches[0];
|
|
916
|
+
if (matches.length !== 1 || !nonempty(product?.displayPrice)) return unavailable(mapping.reference, "priceUnavailable");
|
|
917
|
+
if (product?.type !== "nonConsumable") return unavailable(mapping.reference, "unsupportedProductType");
|
|
918
|
+
return {
|
|
919
|
+
reference: mapping.reference,
|
|
920
|
+
status: "ready",
|
|
921
|
+
source: "appleIap",
|
|
922
|
+
productId: mapping.productId,
|
|
923
|
+
mappingRevision: mapping.mappingRevision,
|
|
924
|
+
displayPrice: product.displayPrice
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function unavailable(reference, reason) {
|
|
928
|
+
return { reference, status: "unavailable", reason, retryable: !["invalidInput", "unsupportedPlatform", "unsupportedProductType", "commercePriceUnavailable"].includes(reason) };
|
|
929
|
+
}
|
|
930
|
+
var isObject2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
931
|
+
var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
932
|
+
var sameReference = (value, reference) => isObject2(value) && value.kind === reference.kind && value.id === reference.id;
|
|
933
|
+
|
|
934
|
+
// ../../core/sdk/iap/src/wix-payments-contract.ts
|
|
935
|
+
var HIMALAYA_SERVE_IAP_ROUTE = { prefix: "/himalaya-serve/v1/iap" };
|
|
936
|
+
var APPLE_SALE_RULES = {
|
|
937
|
+
onlineProgram: { appProductId: (id2) => id2, productType: "NON_CONSUMABLE" }
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// ../../core/sdk/iap/src/mapping-source.ts
|
|
941
|
+
function iapMappingSource() {
|
|
942
|
+
return createWixIapMappingSource({ binding: sharedHostAppBindingSource(), call: hostWixCall });
|
|
943
|
+
}
|
|
944
|
+
function createWixIapMappingSource(deps) {
|
|
945
|
+
return {
|
|
946
|
+
// Only a binding the host actually returned: a read that failed is re-asked on the next load.
|
|
947
|
+
active: async () => await deps.binding.current() !== null,
|
|
948
|
+
mapping: {
|
|
949
|
+
resolve: async ({ scope, references }) => {
|
|
950
|
+
const binding = await deps.binding.current();
|
|
951
|
+
if (!binding) return references.map((reference) => ({ reference, route: "regular" }));
|
|
952
|
+
if (scope.siteId && !sameId(scope.siteId, binding.siteId)) return references.map((reference) => ({ reference, route: "unavailable" }));
|
|
953
|
+
const asked = [...new Set(references.flatMap((reference) => appProductIdOf(reference) ?? []))];
|
|
954
|
+
const mapped = await resolveAll(deps.call, binding, asked);
|
|
955
|
+
return references.map((reference) => decide(reference, binding, mapped));
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function appProductIdOf(reference) {
|
|
961
|
+
return reference.id ? APPLE_SALE_RULES[reference.kind]?.appProductId(reference.id) : void 0;
|
|
962
|
+
}
|
|
963
|
+
var PATH = `${HIMALAYA_SERVE_IAP_ROUTE.prefix}/mappings`;
|
|
964
|
+
var BATCH = 100;
|
|
965
|
+
async function resolveAll(call, binding, ids) {
|
|
966
|
+
const mapped = /* @__PURE__ */ new Map();
|
|
967
|
+
const wanted = new Set(ids);
|
|
968
|
+
for (let at = 0; at < ids.length; at += BATCH) {
|
|
969
|
+
const batch = ids.slice(at, at + BATCH);
|
|
970
|
+
const reply = await call("POST", PATH, { appId: binding.purchasesAppId, appProductIds: batch }, { "wix-site-id": binding.siteId });
|
|
971
|
+
for (const [id2, mapping] of decodeMappings(reply)) if (wanted.has(id2)) mapped.set(id2, mapping);
|
|
972
|
+
}
|
|
973
|
+
return mapped;
|
|
974
|
+
}
|
|
975
|
+
function decodeMappings(reply) {
|
|
976
|
+
const list = reply?.mappings;
|
|
977
|
+
if (!Array.isArray(list)) throw new Error("the mapping route answered without a mappings list");
|
|
978
|
+
return list.map((raw) => {
|
|
979
|
+
const m = raw;
|
|
980
|
+
if (typeof m?.appProductId !== "string" || typeof m.storeProductId !== "string" || !m.storeProductId) {
|
|
981
|
+
throw new Error("the mapping route answered a mapping without its ids");
|
|
982
|
+
}
|
|
983
|
+
return [m.appProductId, { storeProductId: m.storeProductId, type: String(m.type ?? ""), active: m.active === true }];
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function decide(reference, binding, mapped) {
|
|
987
|
+
const id2 = appProductIdOf(reference);
|
|
988
|
+
const mapping = id2 ? mapped.get(id2) : void 0;
|
|
989
|
+
if (!mapping) return { reference, route: "regular" };
|
|
990
|
+
if (!mapping.active || mapping.type !== APPLE_SALE_RULES[reference.kind]?.productType) return { reference, route: "unavailable" };
|
|
991
|
+
return { reference, route: "appleIap", productId: mapping.storeProductId, mappingRevision: `${binding.purchasesAppId}:${mapping.storeProductId}` };
|
|
992
|
+
}
|
|
993
|
+
function sameId(a, b) {
|
|
994
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
995
|
+
}
|
|
996
|
+
function hostWixCall(method, path, body, headers) {
|
|
997
|
+
const bridge = globalThis.himalaya?.wixApi;
|
|
998
|
+
if (!bridge) return Promise.reject(new Error("the host did not install the wixApi bridge"));
|
|
999
|
+
return bridge.call(method, path, body, headers);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ../../core/sdk/iap/src/catalog-pricing-core.ts
|
|
1003
|
+
var POLL_ID = "iapCatalogPricing";
|
|
1004
|
+
var POLL_INTERVAL_MS = 6e4;
|
|
1005
|
+
function createIapCatalogPricingWith(deps) {
|
|
1006
|
+
return deps.source ? enabledPricing(deps.source, deps) : disabledPricing();
|
|
1007
|
+
}
|
|
1008
|
+
function disabledPricing() {
|
|
1009
|
+
return {
|
|
1010
|
+
enabled: false,
|
|
1011
|
+
load: () => Promise.resolve(),
|
|
1012
|
+
refresh: () => Promise.resolve(),
|
|
1013
|
+
view: disabledView,
|
|
1014
|
+
checkoutGuard: (reference) => decisionOf(disabledView(reference)),
|
|
1015
|
+
continueCheckoutGuard: (reference) => decisionOf(disabledView(reference)),
|
|
1016
|
+
appleProduct: () => null,
|
|
1017
|
+
pollDirective: () => void 0,
|
|
1018
|
+
dispose: () => {
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function enabledPricing(source, deps) {
|
|
1023
|
+
let controller;
|
|
1024
|
+
let disposed = false;
|
|
1025
|
+
const settled = /* @__PURE__ */ new Map();
|
|
1026
|
+
let settledScope = "";
|
|
1027
|
+
const rememberSettled = (state) => {
|
|
1028
|
+
const scopeKey = JSON.stringify(state.scope);
|
|
1029
|
+
if (scopeKey !== settledScope) {
|
|
1030
|
+
settled.clear();
|
|
1031
|
+
settledScope = scopeKey;
|
|
1032
|
+
}
|
|
1033
|
+
for (const item of state.items) {
|
|
1034
|
+
const answered = viewOf(item.reference, item);
|
|
1035
|
+
if (!unanswered(answered)) settled.set(referenceKey(item.reference), answered);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
let selling = "active";
|
|
1039
|
+
let lookupRevision = 0;
|
|
1040
|
+
let answeredRoutes;
|
|
1041
|
+
const mapping = {
|
|
1042
|
+
resolve: async (request) => {
|
|
1043
|
+
const asked = lookupRevision;
|
|
1044
|
+
const answer = await source.mapping.resolve(request);
|
|
1045
|
+
if (asked === lookupRevision) answeredRoutes = routesAnswered(answer);
|
|
1046
|
+
return answer;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
const trackLookup = (state) => {
|
|
1050
|
+
if (state.revision !== lookupRevision) {
|
|
1051
|
+
lookupRevision = state.revision;
|
|
1052
|
+
answeredRoutes = void 0;
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
const ensureController = () => controller ?? (controller = createPricingController({
|
|
1056
|
+
mapping,
|
|
1057
|
+
getProducts: deps.getProducts,
|
|
1058
|
+
onState: (state) => {
|
|
1059
|
+
trackLookup(state);
|
|
1060
|
+
rememberSettled(state);
|
|
1061
|
+
deps.onChange?.();
|
|
1062
|
+
}
|
|
1063
|
+
}));
|
|
1064
|
+
const view = (reference) => disposed ? blockedView(reference, "unavailable") : selling === "inactive" ? disabledView(reference) : selling === "unknown" ? sellingUnknownView(reference) : viewOf(reference, controller && itemFor(controller.getState().items, reference));
|
|
1065
|
+
const load = async (input) => {
|
|
1066
|
+
if (disposed) return;
|
|
1067
|
+
selling = source.active ? await source.active().then((active) => active ? "active" : "inactive", () => "unknown") : "active";
|
|
1068
|
+
if (selling !== "active") {
|
|
1069
|
+
deps.onChange?.();
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
await ensureController().load(pricingInput(input, source));
|
|
1073
|
+
};
|
|
1074
|
+
const continueCheckoutGuard = (reference) => {
|
|
1075
|
+
const current = view(reference);
|
|
1076
|
+
if (!unanswered(current) || selling !== "active") return decisionOf(current);
|
|
1077
|
+
if (answeredRoutes) return routeDecision(answeredRoutes.get(referenceKey(reference)) ?? "unavailable");
|
|
1078
|
+
return decisionOf(settled.get(referenceKey(reference)) ?? current);
|
|
1079
|
+
};
|
|
1080
|
+
return {
|
|
1081
|
+
enabled: true,
|
|
1082
|
+
load,
|
|
1083
|
+
refresh: () => controller?.refresh() ?? Promise.resolve(),
|
|
1084
|
+
view,
|
|
1085
|
+
checkoutGuard: (reference) => decisionOf(view(reference)),
|
|
1086
|
+
continueCheckoutGuard,
|
|
1087
|
+
appleProduct: (reference) => disposed || selling !== "active" ? null : appleProductOf(controller && itemFor(controller.getState().items, reference)),
|
|
1088
|
+
pollDirective: (action2) => ({ id: POLL_ID, action: action2, intervalMs: POLL_INTERVAL_MS, immediate: true }),
|
|
1089
|
+
dispose: () => {
|
|
1090
|
+
disposed = true;
|
|
1091
|
+
settled.clear();
|
|
1092
|
+
controller?.dispose();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function pricingInput(input, source) {
|
|
1097
|
+
return { scope: pricingScope(input.scope, source.siteId), platform: input.platform, items: input.items };
|
|
1098
|
+
}
|
|
1099
|
+
function pricingScope(scope, boundSiteId) {
|
|
1100
|
+
return {
|
|
1101
|
+
appId: scope.appId,
|
|
1102
|
+
bundleId: scope.bundleId,
|
|
1103
|
+
siteId: siteIdFor(scope.siteId, boundSiteId),
|
|
1104
|
+
...scope.contextRevision === void 0 ? {} : { contextRevision: scope.contextRevision }
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function siteIdFor(callerSiteId, boundSiteId) {
|
|
1108
|
+
if (callerSiteId && boundSiteId && callerSiteId !== boundSiteId) return "";
|
|
1109
|
+
return callerSiteId || boundSiteId || "";
|
|
1110
|
+
}
|
|
1111
|
+
var referenceKey = (reference) => JSON.stringify([reference.kind, reference.id]);
|
|
1112
|
+
function routesAnswered(answer) {
|
|
1113
|
+
const routes = /* @__PURE__ */ new Map();
|
|
1114
|
+
for (const entry of Array.isArray(answer) ? answer : []) {
|
|
1115
|
+
const reference = entry?.reference;
|
|
1116
|
+
if (typeof reference?.kind !== "string" || typeof reference.id !== "string") continue;
|
|
1117
|
+
const key = referenceKey(reference);
|
|
1118
|
+
const route = entry.route;
|
|
1119
|
+
routes.set(key, routes.has(key) ? "unavailable" : route === "regular" || route === "appleIap" ? route : "unavailable");
|
|
1120
|
+
}
|
|
1121
|
+
return routes;
|
|
1122
|
+
}
|
|
1123
|
+
function routeDecision(route) {
|
|
1124
|
+
return { allowed: route === "regular", route };
|
|
1125
|
+
}
|
|
1126
|
+
function unanswered(view) {
|
|
1127
|
+
return view.route === "resolving" || view.reason === "mappingUnavailable";
|
|
1128
|
+
}
|
|
1129
|
+
function itemFor(items, reference) {
|
|
1130
|
+
return items.find((item) => item.reference.kind === reference.kind && item.reference.id === reference.id);
|
|
1131
|
+
}
|
|
1132
|
+
function appleProductOf(item) {
|
|
1133
|
+
if (item?.status !== "ready" || item.source !== "appleIap") return null;
|
|
1134
|
+
return { productId: item.productId, mappingRevision: item.mappingRevision };
|
|
1135
|
+
}
|
|
1136
|
+
function viewOf(reference, item) {
|
|
1137
|
+
if (!item || item.status === "loading") return blockedView(reference, "resolving");
|
|
1138
|
+
if (item.status === "ready") return readyView(reference, item);
|
|
1139
|
+
return unavailableView(reference, item);
|
|
1140
|
+
}
|
|
1141
|
+
function readyView(reference, item) {
|
|
1142
|
+
return item.source === "appleIap" ? { ...blockedView(reference, "appleIap"), displayPrice: item.displayPrice } : { ...regularView(reference), displayPrice: item.displayPrice };
|
|
1143
|
+
}
|
|
1144
|
+
function unavailableView(reference, item) {
|
|
1145
|
+
if (item.reason === "commercePriceUnavailable") return regularView(reference);
|
|
1146
|
+
return { ...blockedView(reference, "unavailable"), reason: item.reason, retryable: item.retryable };
|
|
1147
|
+
}
|
|
1148
|
+
function blockedView(reference, route) {
|
|
1149
|
+
return { reference: { kind: reference.kind, id: reference.id }, route, displayPrice: "", retryable: false, canStartRegularCheckout: false, purchasable: false };
|
|
1150
|
+
}
|
|
1151
|
+
function regularView(reference) {
|
|
1152
|
+
return { ...blockedView(reference, "regular"), canStartRegularCheckout: true };
|
|
1153
|
+
}
|
|
1154
|
+
function sellingUnknownView(reference) {
|
|
1155
|
+
return { ...blockedView(reference, "unavailable"), reason: "mappingUnavailable", retryable: true };
|
|
1156
|
+
}
|
|
1157
|
+
function disabledView(reference) {
|
|
1158
|
+
return { ...blockedView(reference, "disabled"), canStartRegularCheckout: true };
|
|
1159
|
+
}
|
|
1160
|
+
function decisionOf(view) {
|
|
1161
|
+
return { allowed: view.canStartRegularCheckout, route: view.route };
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// ../../core/sdk/iap/src/catalog-pricing.ts
|
|
1165
|
+
function createIapCatalogPricing(options = {}) {
|
|
1166
|
+
return createIapCatalogPricingWith({ source: iapMappingSource(), getProducts, onChange: options.onChange });
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// ../../core/sdk/iap/src/catalog-price-fields.ts
|
|
1170
|
+
function iapPriceFields(view, copy) {
|
|
1171
|
+
const fields = {
|
|
1172
|
+
priceText: "",
|
|
1173
|
+
priceNote: "",
|
|
1174
|
+
priceLoading: false,
|
|
1175
|
+
hidePriceRetry: true,
|
|
1176
|
+
actionDisabled: !view.canStartRegularCheckout,
|
|
1177
|
+
actionNote: ""
|
|
1178
|
+
};
|
|
1179
|
+
switch (view.route) {
|
|
1180
|
+
case "resolving":
|
|
1181
|
+
return { ...fields, priceText: copy.loading, priceLoading: true };
|
|
1182
|
+
case "appleIap":
|
|
1183
|
+
return { ...fields, priceText: view.displayPrice, priceNote: copy.oneTimePurchase, actionNote: copy.purchaseUnavailable };
|
|
1184
|
+
case "unavailable":
|
|
1185
|
+
return { ...fields, priceText: copy.unavailable, hidePriceRetry: !view.retryable };
|
|
1186
|
+
case "regular":
|
|
1187
|
+
return { ...fields, priceText: view.displayPrice };
|
|
1188
|
+
case "disabled":
|
|
1189
|
+
return fields;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// ../../core/sdk/iap/src/host-session.ts
|
|
1194
|
+
var SIGNED_OUT = {
|
|
1195
|
+
generation: 0,
|
|
1196
|
+
kind: "signedOut",
|
|
1197
|
+
memberId: null,
|
|
1198
|
+
siteId: null,
|
|
1199
|
+
appId: null,
|
|
1200
|
+
ready: false,
|
|
1201
|
+
epoch: 0
|
|
1202
|
+
};
|
|
1203
|
+
var KINDS = /* @__PURE__ */ new Set(["member", "visitor", "owner", "signedOut"]);
|
|
1204
|
+
function createHostSession(source) {
|
|
1205
|
+
let seen = -1;
|
|
1206
|
+
let latest = SIGNED_OUT;
|
|
1207
|
+
const byGeneration = /* @__PURE__ */ new Map();
|
|
1208
|
+
function pull() {
|
|
1209
|
+
const snap = parseSession(source.current());
|
|
1210
|
+
if (snap.generation < seen) return latest;
|
|
1211
|
+
seen = snap.generation;
|
|
1212
|
+
latest = snap;
|
|
1213
|
+
const expectation = expectationOf(snap);
|
|
1214
|
+
if (expectation && !byGeneration.has(snap.generation)) byGeneration.set(snap.generation, expectation);
|
|
1215
|
+
return snap;
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
identityBound: source.identityBound,
|
|
1219
|
+
current() {
|
|
1220
|
+
const snap = pull();
|
|
1221
|
+
return { generation: snap.generation, kind: snap.kind };
|
|
1222
|
+
},
|
|
1223
|
+
identity(session) {
|
|
1224
|
+
pull();
|
|
1225
|
+
return byGeneration.get(session.generation) ?? null;
|
|
1226
|
+
},
|
|
1227
|
+
site() {
|
|
1228
|
+
const snap = pull();
|
|
1229
|
+
return snap.ready ? snap.siteId : null;
|
|
1230
|
+
},
|
|
1231
|
+
subscribe(listener) {
|
|
1232
|
+
return source.subscribe(() => {
|
|
1233
|
+
const before = seen;
|
|
1234
|
+
pull();
|
|
1235
|
+
if (latest.generation >= before) listener();
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function hostSessionFromGlobal() {
|
|
1241
|
+
const api = wixApiGlobal();
|
|
1242
|
+
const session = api?.session;
|
|
1243
|
+
if (!session || typeof session.current !== "function" || typeof session.subscribe !== "function") {
|
|
1244
|
+
return createHostSession({ identityBound: false, current: () => SIGNED_OUT, subscribe: () => () => {
|
|
1245
|
+
} });
|
|
1246
|
+
}
|
|
1247
|
+
return createHostSession({
|
|
1248
|
+
identityBound: api?.identityBound === true,
|
|
1249
|
+
current: () => session.current(),
|
|
1250
|
+
subscribe: (listener) => session.subscribe(listener)
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function memberIdentityOf(source) {
|
|
1254
|
+
return { memberId: (session) => Promise.resolve(source.identity(session)?.memberId ?? null) };
|
|
1255
|
+
}
|
|
1256
|
+
function readIdentityBound() {
|
|
1257
|
+
return wixApiGlobal()?.identityBound === true;
|
|
1258
|
+
}
|
|
1259
|
+
function expectationOf(snap) {
|
|
1260
|
+
if (!snap.ready || snap.kind !== "member" || !snap.memberId || !snap.siteId || !snap.appId) return null;
|
|
1261
|
+
return { generation: snap.generation, kind: "member", memberId: snap.memberId, siteId: snap.siteId, appId: snap.appId };
|
|
1262
|
+
}
|
|
1263
|
+
function parseSession(raw) {
|
|
1264
|
+
if (raw === null || typeof raw !== "object") return SIGNED_OUT;
|
|
1265
|
+
const value = raw;
|
|
1266
|
+
const kind = value.kind;
|
|
1267
|
+
if (typeof value.generation !== "number" || !Number.isSafeInteger(value.generation)) return SIGNED_OUT;
|
|
1268
|
+
if (typeof kind !== "string" || !KINDS.has(kind)) return SIGNED_OUT;
|
|
1269
|
+
return {
|
|
1270
|
+
generation: value.generation,
|
|
1271
|
+
kind,
|
|
1272
|
+
memberId: typeof value.memberId === "string" && value.memberId.length > 0 ? value.memberId : null,
|
|
1273
|
+
siteId: typeof value.siteId === "string" && value.siteId.length > 0 ? value.siteId : null,
|
|
1274
|
+
appId: typeof value.appId === "string" && value.appId.length > 0 ? value.appId : null,
|
|
1275
|
+
ready: value.ready === true,
|
|
1276
|
+
epoch: typeof value.epoch === "number" ? value.epoch : 0
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function wixApiGlobal() {
|
|
1280
|
+
return globalThis.himalaya?.wixApi;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// ../../core/sdk/iap/src/wix-payments-wire.ts
|
|
1284
|
+
function startPaymentCall(scope, wixTransactionId) {
|
|
1285
|
+
return post(scope, "/payments/start", { appId: scope.binding.purchasesAppId, wixTransactionId });
|
|
1286
|
+
}
|
|
1287
|
+
function completePaymentCall(scope, signedTransaction) {
|
|
1288
|
+
return post(scope, "/payments/complete", { appId: scope.binding.purchasesAppId, apple: { signedTransaction } });
|
|
1289
|
+
}
|
|
1290
|
+
function paymentCall(scope, paymentId) {
|
|
1291
|
+
return get2(scope, `/payments/${encodeURIComponent(paymentId)}`);
|
|
1292
|
+
}
|
|
1293
|
+
function isRoutePrefix(prefix) {
|
|
1294
|
+
return /^\/[A-Za-z0-9._~\-/]*[A-Za-z0-9._~-]$/.test(prefix);
|
|
1295
|
+
}
|
|
1296
|
+
function decodeStarted(raw, expected) {
|
|
1297
|
+
if (!isObject3(raw) || !isStoreProductId(raw.storeProductId) || !isUUID(raw.appAccountToken)) return null;
|
|
1298
|
+
const payment = decodePayment(raw.payment, expected);
|
|
1299
|
+
return payment && { payment, storeProductId: raw.storeProductId, appAccountToken: raw.appAccountToken };
|
|
1300
|
+
}
|
|
1301
|
+
function decodePaymentAnswer(raw, expected) {
|
|
1302
|
+
return isObject3(raw) ? decodePayment(raw.payment, expected) : null;
|
|
1303
|
+
}
|
|
1304
|
+
function decodePayment(raw, expected) {
|
|
1305
|
+
if (!isObject3(raw) || !hasPaymentIds(raw) || !echoes(raw, expected) || !hasValidOptionals(raw)) return null;
|
|
1306
|
+
const status = ownValue(STATUSES, raw.status);
|
|
1307
|
+
return status ? toPayment(raw, status) : null;
|
|
1308
|
+
}
|
|
1309
|
+
function hasPaymentIds(raw) {
|
|
1310
|
+
return isUUID(raw.id) && isUUID(raw.appId) && isUUID(raw.memberId) && isUUID(raw.wixTransactionId);
|
|
1311
|
+
}
|
|
1312
|
+
function echoes(raw, expected) {
|
|
1313
|
+
return sameUUID(raw.appId, expected.appId) && (expected.memberId === null || sameUUID(raw.memberId, expected.memberId)) && (expected.paymentId === void 0 || sameUUID(raw.id, expected.paymentId)) && (expected.wixTransactionId === void 0 || sameUUID(raw.wixTransactionId, expected.wixTransactionId));
|
|
1314
|
+
}
|
|
1315
|
+
function hasValidOptionals(raw) {
|
|
1316
|
+
return (raw.appProductId === void 0 || isStoreProductId(raw.appProductId)) && (raw.purchaseTransactionId === void 0 || isUUID(raw.purchaseTransactionId)) && TIMESTAMPS.every((field) => raw[field] === void 0 || isTimestamp(raw[field]));
|
|
1317
|
+
}
|
|
1318
|
+
function toPayment(raw, status) {
|
|
1319
|
+
const optional = {
|
|
1320
|
+
appProductId: raw.appProductId,
|
|
1321
|
+
cancelReason: ownValue(CANCEL_REASONS, raw.cancelReason),
|
|
1322
|
+
purchaseTransactionId: raw.purchaseTransactionId,
|
|
1323
|
+
expiresDate: raw.expiresDate,
|
|
1324
|
+
approvedDate: raw.approvedDate,
|
|
1325
|
+
canceledDate: raw.canceledDate,
|
|
1326
|
+
refundedDate: raw.refundedDate
|
|
1327
|
+
};
|
|
1328
|
+
const ids = { paymentId: raw.id, appId: raw.appId, memberId: raw.memberId, wixTransactionId: raw.wixTransactionId };
|
|
1329
|
+
return { ...ids, status, ...withoutUndefined(optional) };
|
|
1330
|
+
}
|
|
1331
|
+
function classifyFailure(error, operation, codes) {
|
|
1332
|
+
const message = messageOf(error);
|
|
1333
|
+
if (BRIDGE_REFUSAL.test(message)) return failure(operation, "notEnabled");
|
|
1334
|
+
if (PRINCIPAL_CHANGED.test(message)) return failure(operation, "sessionChanged");
|
|
1335
|
+
const http = /^HTTP (\d{3})(?: \[([^\]]+)\])?:/.exec(message);
|
|
1336
|
+
if (!http) return failure(operation, "unavailable");
|
|
1337
|
+
return classifyHttp({ operation, codes, httpStatus: Number(http[1]), code: http[2] ?? "" });
|
|
1338
|
+
}
|
|
1339
|
+
function failure(operation, kind) {
|
|
1340
|
+
return { status: "failed", operation, kind };
|
|
1341
|
+
}
|
|
1342
|
+
function classifyHttp(refusal) {
|
|
1343
|
+
const { operation, codes, httpStatus, code } = refusal;
|
|
1344
|
+
if (code === "PRINCIPAL_CHANGED") return failure(operation, "sessionChanged");
|
|
1345
|
+
if (isOneOf(codes, code)) return { status: "failed", operation, kind: "application", code, httpStatus };
|
|
1346
|
+
return { status: "failed", operation, kind: kindForStatus(httpStatus, code), httpStatus };
|
|
1347
|
+
}
|
|
1348
|
+
function kindForStatus(httpStatus, code) {
|
|
1349
|
+
if (code === "UNAVAILABLE" || httpStatus === 408 || httpStatus === 429 || httpStatus >= 500) return "unavailable";
|
|
1350
|
+
if (httpStatus === 401 || code === "UNAUTHENTICATED") return "authRequired";
|
|
1351
|
+
if (httpStatus === 403 || code === "PERMISSION_DENIED") return "notPermitted";
|
|
1352
|
+
return "unrecognized";
|
|
1353
|
+
}
|
|
1354
|
+
function post(scope, route, body) {
|
|
1355
|
+
return { method: "POST", path: `${scope.prefix}${route}`, body, headers: siteHeaders(scope.binding) };
|
|
1356
|
+
}
|
|
1357
|
+
function get2(scope, route) {
|
|
1358
|
+
return { method: "GET", path: `${scope.prefix}${route}`, headers: siteHeaders(scope.binding) };
|
|
1359
|
+
}
|
|
1360
|
+
function siteHeaders(binding) {
|
|
1361
|
+
return { "wix-site-id": binding.siteId };
|
|
1362
|
+
}
|
|
1363
|
+
function messageOf(error) {
|
|
1364
|
+
const message = error?.message;
|
|
1365
|
+
return typeof message === "string" ? message : "";
|
|
1366
|
+
}
|
|
1367
|
+
function withoutUndefined(value) {
|
|
1368
|
+
const kept = {};
|
|
1369
|
+
for (const key of Object.keys(value)) {
|
|
1370
|
+
if (value[key] !== void 0) kept[key] = value[key];
|
|
1371
|
+
}
|
|
1372
|
+
return kept;
|
|
1373
|
+
}
|
|
1374
|
+
function isOneOf(codes, code) {
|
|
1375
|
+
return codes.includes(code);
|
|
1376
|
+
}
|
|
1377
|
+
function ownValue(table, key) {
|
|
1378
|
+
return typeof key === "string" && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : void 0;
|
|
1379
|
+
}
|
|
1380
|
+
function isObject3(value) {
|
|
1381
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1382
|
+
}
|
|
1383
|
+
function isUUID(value) {
|
|
1384
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
1385
|
+
}
|
|
1386
|
+
function sameUUID(value, expected) {
|
|
1387
|
+
return isUUID(value) && value.toLowerCase() === expected.toLowerCase();
|
|
1388
|
+
}
|
|
1389
|
+
function isStoreProductId(value) {
|
|
1390
|
+
return typeof value === "string" && value.length > 0 && value.length <= 255;
|
|
1391
|
+
}
|
|
1392
|
+
function isTimestamp(value) {
|
|
1393
|
+
return typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
1394
|
+
}
|
|
1395
|
+
var STATUSES = {
|
|
1396
|
+
PENDING: "pending",
|
|
1397
|
+
APPROVING: "approving",
|
|
1398
|
+
APPROVED: "approved",
|
|
1399
|
+
CANCELING: "canceling",
|
|
1400
|
+
CANCELED: "canceled",
|
|
1401
|
+
REFUNDING: "refunding",
|
|
1402
|
+
REFUNDED: "refunded"
|
|
1403
|
+
};
|
|
1404
|
+
var CANCEL_REASONS = { EXPIRED: "expired", REJECTED: "rejected" };
|
|
1405
|
+
var TIMESTAMPS = ["createdDate", "updatedDate", "expiresDate", "approvedDate", "canceledDate", "refundedDate"];
|
|
1406
|
+
var BRIDGE_REFUSAL = /^(wixApi not enabled|capability wix_api not allowed)\b|^NotInstalled\(\)$/;
|
|
1407
|
+
var PRINCIPAL_CHANGED = /^PRINCIPAL_CHANGED\b/;
|
|
1408
|
+
|
|
1409
|
+
// ../../core/sdk/iap/src/wix-payments.ts
|
|
1410
|
+
function createWixIapClient(deps) {
|
|
1411
|
+
function startPayment(input) {
|
|
1412
|
+
if (!isUUID(input.wixTransactionId)) return Promise.resolve(failure("startPayment", "invalidArgument"));
|
|
1413
|
+
return run({
|
|
1414
|
+
operation: "startPayment",
|
|
1415
|
+
codes: START_PAYMENT_CODES,
|
|
1416
|
+
request: (scope) => startPaymentCall(scope, input.wixTransactionId),
|
|
1417
|
+
decode: (raw, scope) => {
|
|
1418
|
+
const started = decodeStarted(raw, { ...expectationOf2(scope), wixTransactionId: input.wixTransactionId });
|
|
1419
|
+
return started && { status: "started", ...started };
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
function completePayment(transaction) {
|
|
1424
|
+
const refusal = refuseProof(transaction);
|
|
1425
|
+
if (refusal) return Promise.resolve(refusal);
|
|
1426
|
+
return run({
|
|
1427
|
+
operation: "completePayment",
|
|
1428
|
+
codes: COMPLETE_PAYMENT_CODES,
|
|
1429
|
+
request: (scope) => completePaymentCall(scope, transaction.signedTransaction),
|
|
1430
|
+
decode: (raw, scope) => {
|
|
1431
|
+
const payment = decodePaymentAnswer(raw, expectationOf2(scope));
|
|
1432
|
+
return payment && { status: "matched", payment };
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
function getPayment(input) {
|
|
1437
|
+
if (!isUUID(input.paymentId)) return Promise.resolve(failure("getPayment", "invalidArgument"));
|
|
1438
|
+
return run({
|
|
1439
|
+
operation: "getPayment",
|
|
1440
|
+
codes: GET_PAYMENT_CODES,
|
|
1441
|
+
request: (scope) => paymentCall(scope, input.paymentId),
|
|
1442
|
+
decode: (raw, scope) => {
|
|
1443
|
+
const payment = decodePaymentAnswer(raw, { ...expectationOf2(scope), paymentId: input.paymentId });
|
|
1444
|
+
return payment && { status: "found", payment };
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
async function run(spec) {
|
|
1449
|
+
const scope = await resolveScope(deps.session.current(), spec.operation);
|
|
1450
|
+
if ("failure" in scope) return scope.failure;
|
|
1451
|
+
const sent = await send(scope, spec);
|
|
1452
|
+
if ("failure" in sent) return sent.failure;
|
|
1453
|
+
return spec.decode(sent.answer, scope) ?? failure(spec.operation, "invalidResponse");
|
|
1454
|
+
}
|
|
1455
|
+
async function resolveScope(session, operation) {
|
|
1456
|
+
if (session.kind !== "member") return refused(operation, "authRequired");
|
|
1457
|
+
if (!isRoutePrefix(deps.route.prefix)) return refused(operation, "notProvisioned");
|
|
1458
|
+
const binding = await read(() => deps.binding.current(), operation);
|
|
1459
|
+
if ("failure" in binding) return binding;
|
|
1460
|
+
if (!binding.value || !isBindingFor(binding.value, deps.host)) return refused(operation, "notProvisioned");
|
|
1461
|
+
const member = await expectedMember(session, operation);
|
|
1462
|
+
if ("failure" in member) return member;
|
|
1463
|
+
return { session, binding: binding.value, prefix: deps.route.prefix, memberId: member.memberId };
|
|
1464
|
+
}
|
|
1465
|
+
async function expectedMember(session, operation) {
|
|
1466
|
+
const identity = deps.identity;
|
|
1467
|
+
if (!identity) return { memberId: null };
|
|
1468
|
+
const memberId = await read(() => identity.memberId(session), operation);
|
|
1469
|
+
if ("failure" in memberId) return memberId;
|
|
1470
|
+
return memberId.value ? { memberId: memberId.value } : refused(operation, "authRequired");
|
|
1471
|
+
}
|
|
1472
|
+
async function send(scope, spec) {
|
|
1473
|
+
const transport = deps.transport ?? bridgeTransport();
|
|
1474
|
+
if (!transport) return refused(spec.operation, "notEnabled");
|
|
1475
|
+
if (!isCurrent(scope.session)) return refused(spec.operation, "sessionChanged");
|
|
1476
|
+
const expectation = boundExpectation2(scope.session, deps.transport == null);
|
|
1477
|
+
if (expectation === "refuse") return refused(spec.operation, "notEnabled");
|
|
1478
|
+
const outcome = await attempt2(transport, spec.request(scope), spec, expectation);
|
|
1479
|
+
return isCurrent(scope.session) ? outcome : refused(spec.operation, "sessionChanged");
|
|
1480
|
+
}
|
|
1481
|
+
function boundExpectation2(session, liveHost) {
|
|
1482
|
+
const source = deps.session;
|
|
1483
|
+
if (liveHost && (source.identityBound !== true || !readIdentityBound())) return "refuse";
|
|
1484
|
+
if (typeof source.identity !== "function") return liveHost ? "refuse" : void 0;
|
|
1485
|
+
return source.identity(session) ?? "refuse";
|
|
1486
|
+
}
|
|
1487
|
+
function isCurrent(session) {
|
|
1488
|
+
return deps.session.current().generation === session.generation;
|
|
1489
|
+
}
|
|
1490
|
+
return { startPayment, completePayment, getPayment };
|
|
1491
|
+
}
|
|
1492
|
+
async function attempt2(transport, call, spec, expectation) {
|
|
1493
|
+
try {
|
|
1494
|
+
const answer = expectation ? await transport.call(call.method, call.path, call.body, call.headers, expectation) : await transport.call(call.method, call.path, call.body, call.headers);
|
|
1495
|
+
return { answer };
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
return { failure: classifyFailure(error, spec.operation, spec.codes) };
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async function read(call, operation) {
|
|
1501
|
+
try {
|
|
1502
|
+
return { value: await call() };
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
return { failure: classifyFailure(error, operation, []) };
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
function refused(operation, kind) {
|
|
1508
|
+
return { failure: failure(operation, kind) };
|
|
1509
|
+
}
|
|
1510
|
+
function expectationOf2(scope) {
|
|
1511
|
+
return { appId: scope.binding.purchasesAppId, memberId: scope.memberId };
|
|
1512
|
+
}
|
|
1513
|
+
function refuseProof(transaction) {
|
|
1514
|
+
if (transaction.environment !== "production" && transaction.environment !== "sandbox") return failure("completePayment", "environmentUnsupported");
|
|
1515
|
+
const signed = transaction.signedTransaction;
|
|
1516
|
+
return typeof signed === "string" && signed.length > 0 && signed.length <= MAX_SIGNED_TRANSACTION ? null : failure("completePayment", "invalidArgument");
|
|
1517
|
+
}
|
|
1518
|
+
function isBindingFor(binding, host) {
|
|
1519
|
+
return binding.himalayaAppId === host.appId && binding.bundleId === host.bundleId && binding.siteId === host.siteId;
|
|
1520
|
+
}
|
|
1521
|
+
function bridgeTransport() {
|
|
1522
|
+
const himalaya = globalThis.himalaya;
|
|
1523
|
+
const bridge = himalaya ? himalaya.wixApi : void 0;
|
|
1524
|
+
return typeof bridge?.call === "function" ? bridge : void 0;
|
|
1525
|
+
}
|
|
1526
|
+
var MAX_SIGNED_TRANSACTION = 2e4;
|
|
1527
|
+
var START_PAYMENT_CODES = [
|
|
1528
|
+
"NOT_PENDING",
|
|
1529
|
+
"WRONG_PAYMENT_METHOD",
|
|
1530
|
+
"NOT_ORDER_BUYER",
|
|
1531
|
+
"UNSUPPORTED_ORDER",
|
|
1532
|
+
"PRODUCT_NOT_AVAILABLE",
|
|
1533
|
+
"ALREADY_OWNED"
|
|
1534
|
+
];
|
|
1535
|
+
var COMPLETE_PAYMENT_CODES = [
|
|
1536
|
+
"INVALID_PURCHASE",
|
|
1537
|
+
"OWNED_BY_ANOTHER_CUSTOMER",
|
|
1538
|
+
"PURCHASE_REVOKED",
|
|
1539
|
+
"NOT_PURCHASED",
|
|
1540
|
+
"ZERO_PRICE",
|
|
1541
|
+
"NO_MATCHING_PAYMENT",
|
|
1542
|
+
"PAYMENT_NOT_PENDING",
|
|
1543
|
+
"APP_NOT_CONFIGURED"
|
|
1544
|
+
];
|
|
1545
|
+
var GET_PAYMENT_CODES = ["NOT_FOUND"];
|
|
1546
|
+
|
|
1547
|
+
// ../../core/sdk/iap/src/index.ts
|
|
1548
|
+
async function getProducts(productIds) {
|
|
1549
|
+
if (!productIds || productIds.length === 0) return [];
|
|
1550
|
+
const r = await dispatch2("capability.iap.getProducts", {
|
|
1551
|
+
// Product ids are reverse-DNS-ish and never contain commas — comma-join
|
|
1552
|
+
// matches the @himalaya/push list-param convention.
|
|
1553
|
+
productIds: productIds.join(",")
|
|
1554
|
+
});
|
|
1555
|
+
return Array.isArray(r?.products) ? r.products : [];
|
|
1556
|
+
}
|
|
1557
|
+
async function purchaseForVerification(input) {
|
|
1558
|
+
if (!nonempty2(input?.productId) || !isUUID2(input?.appAccountToken)) return failure2("invalidArgument");
|
|
1559
|
+
const result = await managedDispatch(() => dispatch2("capability.iap.purchaseForVerification", input));
|
|
1560
|
+
if (result?.status === "purchased" && isAppleTransaction(result.transaction)) {
|
|
1561
|
+
return { status: "purchased", transaction: decodeTransaction(result.transaction) };
|
|
1562
|
+
}
|
|
1563
|
+
if (result?.status === "pending" || result?.status === "cancelled") return { status: result.status };
|
|
1564
|
+
return decodeFailure(result);
|
|
1565
|
+
}
|
|
1566
|
+
async function getUnfinishedTransactions() {
|
|
1567
|
+
const result = await managedDispatch(() => dispatch2("capability.iap.getUnfinishedTransactions"));
|
|
1568
|
+
if (result?.status === "ok" && Array.isArray(result.transactions) && result.transactions.every(isAppleTransaction)) {
|
|
1569
|
+
return { status: "ok", transactions: result.transactions.map(decodeTransaction) };
|
|
1570
|
+
}
|
|
1571
|
+
return decodeFailure(result);
|
|
1572
|
+
}
|
|
1573
|
+
async function finishTransaction(input) {
|
|
1574
|
+
if (!nonempty2(input?.transactionId)) return failure2("invalidArgument");
|
|
1575
|
+
const result = await managedDispatch(() => dispatch2("capability.iap.finishTransaction", input));
|
|
1576
|
+
if (result?.status === "finished" || result?.status === "notFound") return { status: result.status };
|
|
1577
|
+
return decodeFailure(result);
|
|
1578
|
+
}
|
|
1579
|
+
var appleManagedStore = { purchaseForVerification, getUnfinishedTransactions, finishTransaction };
|
|
1580
|
+
function hostAppBindingSource() {
|
|
1581
|
+
let read2;
|
|
1582
|
+
return {
|
|
1583
|
+
current: () => read2 ?? (read2 = readHostBinding().catch((error) => {
|
|
1584
|
+
read2 = void 0;
|
|
1585
|
+
throw error;
|
|
1586
|
+
}))
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
async function readHostBinding() {
|
|
1590
|
+
let answer;
|
|
1591
|
+
try {
|
|
1592
|
+
answer = await dispatch2("capability.iap.appBinding");
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
if (bindingNotOffered(error)) return null;
|
|
1595
|
+
throw new Error("the host could not read the app binding");
|
|
1596
|
+
}
|
|
1597
|
+
if (!isObject4(answer)) throw new Error("the host could not read the app binding");
|
|
1598
|
+
if (answer.status === "bound") return decodeBinding(answer.binding);
|
|
1599
|
+
if (answer.status === "unbound" || answer.status === "failed" && answer.code === "unsupported") return null;
|
|
1600
|
+
throw new Error("the host could not read the app binding");
|
|
1601
|
+
}
|
|
1602
|
+
function bindingNotOffered(error) {
|
|
1603
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1604
|
+
return /(unknown|no capability handler for|needs a fixture for)\W[^\n]{0,20}capability\.iap\.appBinding|capability iap not allowed/i.test(message);
|
|
1605
|
+
}
|
|
1606
|
+
var shared;
|
|
1607
|
+
function sharedHostAppBindingSource() {
|
|
1608
|
+
return shared ?? (shared = hostAppBindingSource());
|
|
1609
|
+
}
|
|
1610
|
+
function decodeBinding(raw) {
|
|
1611
|
+
if (!isObject4(raw)) return null;
|
|
1612
|
+
const { purchasesAppId, himalayaAppId, bundleId, siteId } = raw;
|
|
1613
|
+
if (!isUUID2(purchasesAppId) || !isUUID2(siteId) || !nonempty2(himalayaAppId) || !nonempty2(bundleId)) return null;
|
|
1614
|
+
return { purchasesAppId, himalayaAppId, bundleId, siteId };
|
|
1615
|
+
}
|
|
1616
|
+
var transactionErrors = /* @__PURE__ */ new Set(["unsupported", "unavailable", "invalidArgument", "invalidResponse", "notEnabled", "unsupportedProductType", "unknownProduct", "verificationFailed", "persistenceFailed", "ownershipConflict", "storeError", "busy"]);
|
|
1617
|
+
var failure2 = (code) => ({ status: "failed", code });
|
|
1618
|
+
var nonempty2 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
1619
|
+
var isUUID2 = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) && value.length === 36;
|
|
1620
|
+
var isObject4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1621
|
+
async function managedDispatch(invoke) {
|
|
1622
|
+
try {
|
|
1623
|
+
const value = await invoke();
|
|
1624
|
+
return isObject4(value) ? value : null;
|
|
1625
|
+
} catch {
|
|
1626
|
+
return failure2("unavailable");
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
function decodeFailure(value) {
|
|
1630
|
+
return value?.status === "failed" && transactionErrors.has(value.code) ? failure2(value.code) : failure2("invalidResponse");
|
|
1631
|
+
}
|
|
1632
|
+
function isAppleTransaction(value) {
|
|
1633
|
+
if (!isObject4(value)) return false;
|
|
1634
|
+
return value.store === "apple" && typeof value.signedTransaction === "string" && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value.signedTransaction) && nonempty2(value.transactionId) && nonempty2(value.originalTransactionId) && nonempty2(value.productId) && (value.appAccountToken === null || isUUID2(value.appAccountToken)) && ["production", "sandbox", "xcode", "unknown"].includes(value.environment);
|
|
1635
|
+
}
|
|
1636
|
+
function decodeTransaction(value) {
|
|
1637
|
+
const { store, signedTransaction, transactionId, originalTransactionId, productId, appAccountToken, environment } = value;
|
|
1638
|
+
return { store, signedTransaction, transactionId, originalTransactionId, productId, appAccountToken, environment };
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// ../../stdlib/flows/iap-checkout/src/checkout-source.ts
|
|
1642
|
+
var HIMALAYA_SERVE_CHECKOUT_ROUTES = {
|
|
1643
|
+
wixIap: HIMALAYA_SERVE_IAP_ROUTE,
|
|
1644
|
+
cashier: { prefix: HIMALAYA_SERVE_IAP_ROUTE.prefix }
|
|
1645
|
+
};
|
|
1646
|
+
function appleIapCheckoutRoutes() {
|
|
1647
|
+
return HIMALAYA_SERVE_CHECKOUT_ROUTES;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// ../../stdlib/flows/iap-checkout/src/cashier-charges.ts
|
|
1651
|
+
async function ensureAppleIapCharge(bridge, route, orderId) {
|
|
1652
|
+
if (!isRoutePrefix2(route.prefix)) return { status: "notConfigured" };
|
|
1653
|
+
if (!orderId) return { status: "invalidResponse" };
|
|
1654
|
+
const listed = await listOrderCharges(bridge, route, orderId);
|
|
1655
|
+
if (listed.status !== "ok") return listed;
|
|
1656
|
+
return decideFromCharges(listed.charges) ?? chargeThenReconcile(bridge, route, orderId);
|
|
1657
|
+
}
|
|
1658
|
+
async function listOrderCharges(bridge, route, orderId) {
|
|
1659
|
+
const answer = await bridge.call("GET", chargesPath(route, orderId));
|
|
1660
|
+
if (answer.status !== "ok") return failureOf(answer);
|
|
1661
|
+
const charges = decodeCharges(answer.body);
|
|
1662
|
+
return charges ? { status: "ok", charges } : { status: "invalidResponse" };
|
|
1663
|
+
}
|
|
1664
|
+
async function chargeThenReconcile(bridge, route, orderId) {
|
|
1665
|
+
const answer = await bridge.call("POST", chargesPath(route, orderId), APPLE_IAP_PAYMENT);
|
|
1666
|
+
if (answer.status === "ok") return chargedFrom(answer.body) ?? reconcile(bridge, route, orderId);
|
|
1667
|
+
if (answer.status === "sessionChanged" || answer.status === "notEnabled") return { status: answer.status };
|
|
1668
|
+
return reconcile(bridge, route, orderId);
|
|
1669
|
+
}
|
|
1670
|
+
async function reconcile(bridge, route, orderId) {
|
|
1671
|
+
const listed = await listOrderCharges(bridge, route, orderId);
|
|
1672
|
+
if (listed.status !== "ok") return listed;
|
|
1673
|
+
const decided = decideFromCharges(listed.charges);
|
|
1674
|
+
return decided && decided.status !== "closed" ? decided : { status: "invalidResponse" };
|
|
1675
|
+
}
|
|
1676
|
+
function decideFromCharges(charges) {
|
|
1677
|
+
const live = charges.filter((charge) => !RETRYABLE_AFTER.has(charge.status));
|
|
1678
|
+
if (live.length === 0) return void 0;
|
|
1679
|
+
if (live.some((charge) => CLOSED.has(charge.status))) return { status: "closed" };
|
|
1680
|
+
const apple = live.find((charge) => charge.paymentMethod === APPLE_IAP);
|
|
1681
|
+
if (!apple) return { status: "inOtherPayment" };
|
|
1682
|
+
if (apple.status === "APPROVED") return { status: "approved", transactionId: apple.transactionId };
|
|
1683
|
+
return isAwaitingApple(apple) ? { status: "pending", transactionId: apple.transactionId } : { status: "invalidResponse" };
|
|
1684
|
+
}
|
|
1685
|
+
function chargedFrom(body) {
|
|
1686
|
+
const charge = isObject5(body) ? decodeCharge(body.charge) : null;
|
|
1687
|
+
return charge && isAwaitingApple(charge) ? { status: "pending", transactionId: charge.transactionId } : null;
|
|
1688
|
+
}
|
|
1689
|
+
function isAwaitingApple(charge) {
|
|
1690
|
+
return charge.paymentMethod === APPLE_IAP && AWAITING_PAYMENT.has(charge.status);
|
|
1691
|
+
}
|
|
1692
|
+
function decodeCharges(body) {
|
|
1693
|
+
if (!isObject5(body)) return null;
|
|
1694
|
+
const raw = body.charges ?? [];
|
|
1695
|
+
if (!Array.isArray(raw)) return null;
|
|
1696
|
+
const charges = raw.map(decodeCharge);
|
|
1697
|
+
return charges.every((charge) => charge !== null) ? charges : null;
|
|
1698
|
+
}
|
|
1699
|
+
function decodeCharge(raw) {
|
|
1700
|
+
if (!isObject5(raw) || !isNonEmpty2(raw.chargeId) || !isNonEmpty2(raw.transactionId)) return null;
|
|
1701
|
+
const state = isObject5(raw.state) && isObject5(raw.state.transactionStatus) ? raw.state.transactionStatus : {};
|
|
1702
|
+
const method = isNonEmpty2(raw.paymentMethodId) ? raw.paymentMethodId : isNonEmpty2(raw.paymentMethod) ? raw.paymentMethod : "";
|
|
1703
|
+
return { chargeId: raw.chargeId, transactionId: raw.transactionId, status: isNonEmpty2(state.status) ? state.status : "UNDEFINED", paymentMethod: method };
|
|
1704
|
+
}
|
|
1705
|
+
function failureOf(answer) {
|
|
1706
|
+
return answer.status === "http" ? { status: "refused" } : { status: answer.status };
|
|
1707
|
+
}
|
|
1708
|
+
function chargesPath(route, orderId) {
|
|
1709
|
+
return `${route.prefix}/orders/${encodeURIComponent(orderId)}/charges`;
|
|
1710
|
+
}
|
|
1711
|
+
function isRoutePrefix2(prefix) {
|
|
1712
|
+
return /^\/[A-Za-z0-9._~\-/]*[A-Za-z0-9._~-]$/.test(prefix);
|
|
1713
|
+
}
|
|
1714
|
+
function isObject5(value) {
|
|
1715
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1716
|
+
}
|
|
1717
|
+
function isNonEmpty2(value) {
|
|
1718
|
+
return typeof value === "string" && value.length > 0;
|
|
1719
|
+
}
|
|
1720
|
+
var APPLE_IAP = "appleIAP";
|
|
1721
|
+
var APPLE_IAP_PAYMENT = { payment: { details: { paymentMethod: APPLE_IAP } } };
|
|
1722
|
+
var AWAITING_PAYMENT = /* @__PURE__ */ new Set(["INITIALIZATION", "INITIALIZED", "IN_PROCESS", "PENDING_BUYER"]);
|
|
1723
|
+
var RETRYABLE_AFTER = /* @__PURE__ */ new Set(["DECLINED", "FAILED"]);
|
|
1724
|
+
var CLOSED = /* @__PURE__ */ new Set(["BUYER_CANCELED", "TIMEOUT", "VOIDED", "REFUND", "CHARGE_BACK", "OFFLINE"]);
|
|
1725
|
+
|
|
1726
|
+
// ../../stdlib/flows/iap-checkout/src/iap-finish-policy.ts
|
|
1727
|
+
var COMPLETE_PAYMENT_REFUSALS = {
|
|
1728
|
+
// After recordPurchase (PurchaseMatcher.refusalOf / orphan): the purchase is recorded.
|
|
1729
|
+
NOT_PURCHASED: "refusedFinal",
|
|
1730
|
+
PURCHASE_REVOKED: "refusedFinal",
|
|
1731
|
+
ZERO_PRICE: "refusedFinal",
|
|
1732
|
+
NO_MATCHING_PAYMENT: "refusedFinal",
|
|
1733
|
+
PAYMENT_NOT_PENDING: "refusedFinal",
|
|
1734
|
+
// Before recordPurchase: nothing is recorded. Held pending owner confirmation.
|
|
1735
|
+
INVALID_PURCHASE: "hold",
|
|
1736
|
+
APP_NOT_CONFIGURED: "hold",
|
|
1737
|
+
OWNED_BY_ANOTHER_CUSTOMER: "ownershipConflict"
|
|
1738
|
+
};
|
|
1739
|
+
function isFinalRefusal(status) {
|
|
1740
|
+
return status === "canceling" || status === "canceled" || status === "refunding" || status === "refunded";
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// ../../stdlib/flows/iap-checkout/src/order-refs.ts
|
|
1744
|
+
var NEEDS_SUPPORT_HOLD_MS = 6048e5;
|
|
1745
|
+
function pruneExpiredHolds(partition, now) {
|
|
1746
|
+
const expired = partition.refs().filter((ref) => HOLDS.has(ref.stage) && now - ref.updatedAt >= NEEDS_SUPPORT_HOLD_MS);
|
|
1747
|
+
return expired.filter((ref) => partition.remove(ref.key)).map((ref) => ref.key);
|
|
1748
|
+
}
|
|
1749
|
+
var MAX_ORDER_REFS = 16;
|
|
1750
|
+
function createWorkerOrderRefStore(storage) {
|
|
1751
|
+
const kv = () => storage ?? hostStorage();
|
|
1752
|
+
function open2(key) {
|
|
1753
|
+
if (!isPartitionKey(key)) return null;
|
|
1754
|
+
const location = partitionKey(key);
|
|
1755
|
+
if (readRefs(kv(), location) === null) return null;
|
|
214
1756
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
1757
|
+
refs: () => readRefs(kv(), location) ?? [],
|
|
1758
|
+
put: (ref) => writeWith(location, key, (refs) => upsert(refs, ref)),
|
|
1759
|
+
remove: (refKey) => writeWith(location, key, (refs) => refs.filter((stored) => stored.key !== refKey))
|
|
218
1760
|
};
|
|
1761
|
+
}
|
|
1762
|
+
function writeWith(location, key, change) {
|
|
1763
|
+
const store = kv();
|
|
1764
|
+
if (!store) return false;
|
|
1765
|
+
const current = readRefs(store, location);
|
|
1766
|
+
const next = current && change(current);
|
|
1767
|
+
if (!next) return false;
|
|
1768
|
+
if (next.length > 0 && !updateIndex(store, key, true)) return false;
|
|
1769
|
+
if (!writeVerified(store, location, serialize({ v: VERSION, refs: next }))) return false;
|
|
1770
|
+
if (next.length === 0) updateIndex(store, key, false);
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
function hasRefs(app) {
|
|
1774
|
+
if (!isUUID3(app.purchasesAppId) || !isUUID3(app.siteId)) return null;
|
|
1775
|
+
const members = readIndex(kv(), indexKey(app));
|
|
1776
|
+
return members && members.length > 0;
|
|
1777
|
+
}
|
|
1778
|
+
return { open: open2, hasRefs };
|
|
1779
|
+
}
|
|
1780
|
+
function upsert(refs, ref) {
|
|
1781
|
+
const others = refs.filter((stored) => stored.key !== ref.key);
|
|
1782
|
+
if (others.length < MAX_ORDER_REFS) return [...others, ref];
|
|
1783
|
+
const evictable = others.filter((stored) => EVICTABLE.has(stored.stage)).sort((a, b) => a.updatedAt - b.updatedAt)[0];
|
|
1784
|
+
return evictable ? [...others.filter((stored) => stored !== evictable), ref] : null;
|
|
1785
|
+
}
|
|
1786
|
+
function updateIndex(store, key, holdsRefs) {
|
|
1787
|
+
const location = indexKey(key);
|
|
1788
|
+
const members = readIndex(store, location);
|
|
1789
|
+
if (members === null) return false;
|
|
1790
|
+
const member = memberKey(key.memberId);
|
|
1791
|
+
const others = members.filter((stored) => stored !== member);
|
|
1792
|
+
const next = holdsRefs ? [...others, member] : others;
|
|
1793
|
+
return next.length === members.length && next.every((m, i) => m === members[i]) || writeVerified(store, location, serialize({ v: VERSION, members: next }));
|
|
1794
|
+
}
|
|
1795
|
+
function readRefs(store, location) {
|
|
1796
|
+
const parsed = readVersioned(store, location);
|
|
1797
|
+
if (parsed === "empty") return [];
|
|
1798
|
+
if (!parsed || !Array.isArray(parsed.refs) || !parsed.refs.every(isOrderRef)) return null;
|
|
1799
|
+
return parsed.refs;
|
|
1800
|
+
}
|
|
1801
|
+
function readIndex(store, location) {
|
|
1802
|
+
const parsed = readVersioned(store, location);
|
|
1803
|
+
if (parsed === "empty") return [];
|
|
1804
|
+
if (!parsed || !Array.isArray(parsed.members) || !parsed.members.every(isMemberKey)) return null;
|
|
1805
|
+
return parsed.members;
|
|
1806
|
+
}
|
|
1807
|
+
function readVersioned(store, location) {
|
|
1808
|
+
if (!store) return null;
|
|
1809
|
+
const raw = safely(() => store.getItem(location));
|
|
1810
|
+
if (raw === void 0) return null;
|
|
1811
|
+
if (raw === null || raw === "") return "empty";
|
|
1812
|
+
const parsed = safely(() => JSON.parse(raw));
|
|
1813
|
+
return isObject6(parsed) && parsed.v === VERSION ? parsed : null;
|
|
1814
|
+
}
|
|
1815
|
+
function writeVerified(store, location, value) {
|
|
1816
|
+
if (!store) return false;
|
|
1817
|
+
return safely(() => {
|
|
1818
|
+
store.setItem(location, value);
|
|
1819
|
+
return store.getItem(location) === value;
|
|
1820
|
+
}) === true;
|
|
1821
|
+
}
|
|
1822
|
+
function isOrderRef(value) {
|
|
1823
|
+
if (!isObject6(value) || typeof value.key !== "string" || !STAGES.has(value.stage)) return false;
|
|
1824
|
+
if (typeof value.updatedAt !== "number") return false;
|
|
1825
|
+
const optional = ["kind", "cashierOrderId", "wixTransactionId", "paymentId", "storeProductId", "appProductId", "appleTransactionId", "appAccountToken"];
|
|
1826
|
+
if (!optional.every((field) => value[field] === void 0 || typeof value[field] === "string")) return false;
|
|
1827
|
+
return value.verticalRef === void 0 || isStringMap(value.verticalRef);
|
|
1828
|
+
}
|
|
1829
|
+
function isPartitionKey(key) {
|
|
1830
|
+
return isUUID3(key.purchasesAppId) && isUUID3(key.siteId) && isUUID3(key.memberId);
|
|
1831
|
+
}
|
|
1832
|
+
function partitionKey(key) {
|
|
1833
|
+
return `${PREFIX}.${key.purchasesAppId}.${key.siteId}.${memberKey(key.memberId)}`;
|
|
1834
|
+
}
|
|
1835
|
+
function memberKey(memberId) {
|
|
1836
|
+
const text2 = memberId.toLowerCase();
|
|
1837
|
+
let h1 = 3735928559;
|
|
1838
|
+
let h2 = 1103547991;
|
|
1839
|
+
for (let i = 0; i < text2.length; i++) {
|
|
1840
|
+
const code = text2.charCodeAt(i);
|
|
1841
|
+
h1 = Math.imul(h1 ^ code, 2654435761);
|
|
1842
|
+
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
1843
|
+
}
|
|
1844
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
1845
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
1846
|
+
return `m${(4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0")}`;
|
|
1847
|
+
}
|
|
1848
|
+
function isMemberKey(value) {
|
|
1849
|
+
return typeof value === "string" && /^m[0-9a-f]{14}$/.test(value);
|
|
1850
|
+
}
|
|
1851
|
+
function indexKey(app) {
|
|
1852
|
+
return `${PREFIX}.${app.purchasesAppId}.${app.siteId}.members`;
|
|
1853
|
+
}
|
|
1854
|
+
function hostStorage() {
|
|
1855
|
+
const storage = globalThis.himalaya?.storage;
|
|
1856
|
+
return typeof storage?.getItem === "function" && typeof storage.setItem === "function" ? storage : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
function safely(read2) {
|
|
1859
|
+
try {
|
|
1860
|
+
return read2();
|
|
219
1861
|
} catch {
|
|
220
|
-
return
|
|
1862
|
+
return void 0;
|
|
221
1863
|
}
|
|
222
1864
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
1865
|
+
function serialize(value) {
|
|
1866
|
+
return JSON.stringify(value);
|
|
1867
|
+
}
|
|
1868
|
+
function isStringMap(value) {
|
|
1869
|
+
return isObject6(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1870
|
+
}
|
|
1871
|
+
function isObject6(value) {
|
|
1872
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1873
|
+
}
|
|
1874
|
+
function isUUID3(value) {
|
|
1875
|
+
return typeof value === "string" && /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(value);
|
|
1876
|
+
}
|
|
1877
|
+
var VERSION = 1;
|
|
1878
|
+
var PREFIX = "himi.iap.wix-checkout.v1";
|
|
1879
|
+
var STAGES = /* @__PURE__ */ new Set(["ordered", "charged", "started", "submitted", "held", "needsSupport"]);
|
|
1880
|
+
var HOLDS = /* @__PURE__ */ new Set(["held", "needsSupport"]);
|
|
1881
|
+
var EVICTABLE = /* @__PURE__ */ new Set(["ordered", "charged", "started"]);
|
|
1882
|
+
|
|
1883
|
+
// ../../stdlib/flows/iap-checkout/src/wix-bridge.ts
|
|
1884
|
+
function bindBridge(session, provider, bridge) {
|
|
1885
|
+
const isCurrent = () => session.kind === "member" && provider.current().generation === session.generation;
|
|
1886
|
+
async function call(method, path, body) {
|
|
1887
|
+
if (!isCurrent()) return SESSION_CHANGED;
|
|
1888
|
+
const expectation = boundExpectation(session, provider, bridge);
|
|
1889
|
+
if (expectation === "refuse") return { status: "notEnabled" };
|
|
1890
|
+
const outcome = await attempt3(bridge, method, path, body, expectation);
|
|
1891
|
+
return isCurrent() ? outcome : SESSION_CHANGED;
|
|
227
1892
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
1893
|
+
return { call, isCurrent };
|
|
1894
|
+
}
|
|
1895
|
+
function hostBridge() {
|
|
1896
|
+
const himalaya = globalThis.himalaya;
|
|
1897
|
+
const bridge = himalaya ? himalaya.wixApi : void 0;
|
|
1898
|
+
return typeof bridge?.call === "function" ? bridge : void 0;
|
|
1899
|
+
}
|
|
1900
|
+
var MISSING_BRIDGE = { call: () => Promise.reject(new Error("wixApi not enabled")) };
|
|
1901
|
+
async function attempt3(bridge, method, path, body, expectation) {
|
|
1902
|
+
try {
|
|
1903
|
+
const answer = expectation ? await bridge.call(method, path, body, void 0, expectation) : await bridge.call(method, path, body);
|
|
1904
|
+
return { status: "ok", body: answer };
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
return refusalOf(error);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
function boundExpectation(session, provider, bridge) {
|
|
1910
|
+
const source = provider;
|
|
1911
|
+
const live = isLiveHost(bridge);
|
|
1912
|
+
if (live && (source.identityBound !== true || !readIdentityBound())) return "refuse";
|
|
1913
|
+
if (typeof source.identity !== "function") return live ? "refuse" : void 0;
|
|
1914
|
+
return source.identity(session) ?? "refuse";
|
|
1915
|
+
}
|
|
1916
|
+
function isLiveHost(bridge) {
|
|
1917
|
+
const api = globalThis.himalaya?.wixApi;
|
|
1918
|
+
return api != null && (bridge === api || bridge.call === api.call);
|
|
1919
|
+
}
|
|
1920
|
+
function refusalOf(error) {
|
|
1921
|
+
const message = error?.message;
|
|
1922
|
+
const text2 = typeof message === "string" ? message : "";
|
|
1923
|
+
if (BRIDGE_REFUSAL2.test(text2)) return { status: "notEnabled" };
|
|
1924
|
+
if (PRINCIPAL_CHANGED2.test(text2)) return SESSION_CHANGED;
|
|
1925
|
+
const http = /^HTTP (\d{3})(?: \[([^\]]+)\])?:/.exec(text2);
|
|
1926
|
+
if (!http) return { status: "unavailable" };
|
|
1927
|
+
return httpRefusal(Number(http[1]), http[2] ?? "");
|
|
1928
|
+
}
|
|
1929
|
+
function httpRefusal(httpStatus, code) {
|
|
1930
|
+
if (code === "PRINCIPAL_CHANGED") return SESSION_CHANGED;
|
|
1931
|
+
if (code === "UNAVAILABLE" || httpStatus === 408 || httpStatus === 429 || httpStatus >= 500) return { status: "unavailable" };
|
|
1932
|
+
return { status: "http", httpStatus, code };
|
|
1933
|
+
}
|
|
1934
|
+
var SESSION_CHANGED = { status: "sessionChanged" };
|
|
1935
|
+
var BRIDGE_REFUSAL2 = /^(wixApi not enabled|capability wix_api not allowed)\b|^NotInstalled\(\)$/;
|
|
1936
|
+
var PRINCIPAL_CHANGED2 = /^PRINCIPAL_CHANGED\b/;
|
|
1937
|
+
|
|
1938
|
+
// ../../stdlib/flows/iap-checkout/src/iap-order-backend.ts
|
|
1939
|
+
function createWixOrderBackend(deps) {
|
|
1940
|
+
let cached = null;
|
|
1941
|
+
const now = () => deps.clock.now();
|
|
1942
|
+
const isCurrent = (session) => session.kind === "member" && deps.session.current().generation === session.generation;
|
|
1943
|
+
const adapterFor = (kind) => deps.adapters.find((adapter) => adapter.kind === kind);
|
|
1944
|
+
async function memberOf(session) {
|
|
1945
|
+
if (cached?.generation === session.generation) return cached.member;
|
|
1946
|
+
const binding = await readBinding(deps.binding);
|
|
1947
|
+
if (!isCurrent(session)) return STALE2;
|
|
1948
|
+
if (binding === void 0) return retry("unavailable");
|
|
1949
|
+
if (!binding) return stopWith("rejected", "notConfigured");
|
|
1950
|
+
const memberId = await readMemberId(deps.identity, session);
|
|
1951
|
+
if (!isCurrent(session)) return STALE2;
|
|
1952
|
+
if (memberId === void 0) return retry("unavailable");
|
|
1953
|
+
if (!memberId) return SIGN_IN;
|
|
1954
|
+
const member = memberFor(binding, memberId);
|
|
1955
|
+
cached = { generation: session.generation, member };
|
|
1956
|
+
return member;
|
|
1957
|
+
}
|
|
1958
|
+
function memberFor(binding, memberId) {
|
|
1959
|
+
const partition = deps.refs.open({ purchasesAppId: binding.purchasesAppId, siteId: binding.siteId, memberId });
|
|
1960
|
+
return { memberId, scopeKey: `${binding.purchasesAppId}:${memberId.toLowerCase()}`, partition };
|
|
1961
|
+
}
|
|
1962
|
+
async function getPurchaseContext(session, catalogRef) {
|
|
1963
|
+
const sale = await saleFor(session, catalogRef);
|
|
1964
|
+
if (isStop(sale)) return contextResult(catalogRef, sale);
|
|
1965
|
+
const ordered = await orderFor(sale);
|
|
1966
|
+
if (isStop(ordered)) return contextResult(catalogRef, ordered);
|
|
1967
|
+
const charged = await chargeFor(sale, ordered, { reordered: false });
|
|
1968
|
+
if (isStop(charged)) return contextResult(catalogRef, charged);
|
|
1969
|
+
return startFor(sale, charged);
|
|
1970
|
+
}
|
|
1971
|
+
async function saleFor(session, catalogRef) {
|
|
1972
|
+
const sold = deps.itemFor(catalogRef);
|
|
1973
|
+
const adapter = sold && adapterFor(sold.item.reference.kind);
|
|
1974
|
+
if (!sold || !adapter) return stopWith("rejected", "unsupportedItem");
|
|
1975
|
+
const eligibility = adapter.eligibility(sold.item);
|
|
1976
|
+
if (!eligibility.eligible) return stopWith("rejected", eligibility.reason);
|
|
1977
|
+
const member = await memberOf(session);
|
|
1978
|
+
if (isStop(member)) return member;
|
|
1979
|
+
if (!member.partition) return stopWith("rejected", "storageUnavailable");
|
|
1980
|
+
if (!isCurrent(session)) return STALE2;
|
|
1981
|
+
pruneExpiredHolds(member.partition, now());
|
|
1982
|
+
return {
|
|
1983
|
+
catalogRef,
|
|
1984
|
+
item: sold.item,
|
|
1985
|
+
appleProduct: sold.appleProduct,
|
|
1986
|
+
adapter,
|
|
1987
|
+
member,
|
|
1988
|
+
partition: member.partition,
|
|
1989
|
+
bridge: bindBridge(session, deps.session, deps.bridge)
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
async function orderFor(sale) {
|
|
1993
|
+
const existing = refAt(sale.partition, sale.catalogRef);
|
|
1994
|
+
if (existing?.stage === "submitted") return stopWith("rejected", "paymentInProgress");
|
|
1995
|
+
if (existing?.stage === "held") return stopWith("rejected", "paymentHeld");
|
|
1996
|
+
if (existing?.stage === "needsSupport") return stopWith("rejected", "needsSupport");
|
|
1997
|
+
if (deps.productBusy?.(sale.appleProduct.productId)) return stopWith("rejected", "paymentInProgress");
|
|
1998
|
+
const attempt4 = existing?.cashierOrderId && existing.verticalRef ? await reopenOrOpen(sale, { cashierOrderId: existing.cashierOrderId, verticalRef: existing.verticalRef }) : await sale.adapter.openOrder(sale.item, sale.bridge);
|
|
1999
|
+
return recordOrder(sale, existing, attempt4);
|
|
2000
|
+
}
|
|
2001
|
+
async function reopenOrOpen(sale, order) {
|
|
2002
|
+
const reopened = await sale.adapter.reopenOrder(order, sale.bridge);
|
|
2003
|
+
const closed = reopened.status === "refused" && reopened.reason === "orderClosed";
|
|
2004
|
+
return closed ? sale.adapter.openOrder(sale.item, sale.bridge) : reopened;
|
|
2005
|
+
}
|
|
2006
|
+
function recordOrder(sale, existing, attempt4) {
|
|
2007
|
+
if (attempt4.status === "granted") return alreadyOwned(sale);
|
|
2008
|
+
if (attempt4.status === "refused") return stopWith("rejected", attempt4.reason);
|
|
2009
|
+
if (attempt4.status !== "open") return attempt4.status === "sessionChanged" ? STALE2 : retry("unavailable");
|
|
2010
|
+
const kept = existing && existing.cashierOrderId === attempt4.cashierOrderId ? existing : { key: sale.catalogRef, updatedAt: 0 };
|
|
2011
|
+
return persist(sale, {
|
|
2012
|
+
...kept,
|
|
2013
|
+
key: sale.catalogRef,
|
|
2014
|
+
stage: "ordered",
|
|
2015
|
+
kind: sale.adapter.kind,
|
|
2016
|
+
verticalRef: attempt4.verticalRef,
|
|
2017
|
+
cashierOrderId: attempt4.cashierOrderId,
|
|
2018
|
+
updatedAt: now()
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
async function chargeFor(sale, ref, options) {
|
|
2022
|
+
const charge = await ensureAppleIapCharge(sale.bridge, deps.cashier, ref.cashierOrderId ?? "");
|
|
2023
|
+
switch (charge.status) {
|
|
2024
|
+
case "pending":
|
|
2025
|
+
return persist(sale, { ...ref, stage: "charged", wixTransactionId: charge.transactionId, updatedAt: now() });
|
|
2026
|
+
case "approved":
|
|
2027
|
+
return alreadyPaid(sale, ref);
|
|
2028
|
+
case "closed":
|
|
2029
|
+
return options.reordered ? stopWith("rejected", "orderClosed") : reorder(sale);
|
|
2030
|
+
case "inOtherPayment":
|
|
2031
|
+
return stopWith("rejected", "orderRefused");
|
|
2032
|
+
case "refused":
|
|
2033
|
+
return stopWith("rejected", "chargeFailed");
|
|
2034
|
+
case "notConfigured":
|
|
2035
|
+
case "notEnabled":
|
|
2036
|
+
return stopWith("rejected", "notConfigured");
|
|
2037
|
+
case "sessionChanged":
|
|
2038
|
+
return STALE2;
|
|
2039
|
+
default:
|
|
2040
|
+
return retry("unavailable");
|
|
245
2041
|
}
|
|
246
|
-
if (r.status === "cancelled") return { kind: "cancelled" };
|
|
247
2042
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
2043
|
+
async function reorder(sale) {
|
|
2044
|
+
if (!forget(sale, sale.catalogRef)) return stopWith("rejected", "storageUnavailable");
|
|
2045
|
+
const recorded = recordOrder(sale, void 0, await sale.adapter.openOrder(sale.item, sale.bridge));
|
|
2046
|
+
return isStop(recorded) ? recorded : chargeFor(sale, recorded, { reordered: true });
|
|
2047
|
+
}
|
|
2048
|
+
async function alreadyPaid(sale, ref) {
|
|
2049
|
+
const access = await sale.adapter.readAccess(ref.verticalRef ?? {}, sale.bridge);
|
|
2050
|
+
if (access === "granted") return alreadyOwned(sale);
|
|
2051
|
+
if (access === "sessionChanged") return STALE2;
|
|
2052
|
+
return access === "unavailable" ? retry("unavailable") : stopWith("rejected", "paymentInProgress");
|
|
2053
|
+
}
|
|
2054
|
+
async function startFor(sale, ref) {
|
|
2055
|
+
if (!sale.bridge.isCurrent()) return contextResult(sale.catalogRef, STALE2);
|
|
2056
|
+
const started = await deps.wixIap.startPayment({ wixTransactionId: ref.wixTransactionId ?? "" });
|
|
2057
|
+
if (!sale.bridge.isCurrent()) return contextResult(sale.catalogRef, STALE2);
|
|
2058
|
+
if (started.status !== "started") return contextResult(sale.catalogRef, await startRefusal(sale, ref, started));
|
|
2059
|
+
const mismatch = startedMismatch(sale, started);
|
|
2060
|
+
if (mismatch) return contextResult(sale.catalogRef, stopWith("rejected", mismatch));
|
|
2061
|
+
const recorded = persist(sale, {
|
|
2062
|
+
...ref,
|
|
2063
|
+
stage: "started",
|
|
2064
|
+
paymentId: started.payment.paymentId,
|
|
2065
|
+
storeProductId: started.storeProductId,
|
|
2066
|
+
appAccountToken: started.appAccountToken.toLowerCase(),
|
|
2067
|
+
...started.payment.appProductId ? { appProductId: started.payment.appProductId } : {},
|
|
2068
|
+
updatedAt: now()
|
|
2069
|
+
});
|
|
2070
|
+
if (isStop(recorded)) return contextResult(sale.catalogRef, recorded);
|
|
2071
|
+
return { status: "ok", context: { productId: started.storeProductId, appAccountToken: started.appAccountToken, scopeKey: sale.member.scopeKey } };
|
|
2072
|
+
}
|
|
2073
|
+
function startedMismatch(sale, started) {
|
|
2074
|
+
return sale.appleProduct.productId === started.storeProductId ? null : "productChanged";
|
|
2075
|
+
}
|
|
2076
|
+
async function startRefusal(sale, ref, failure3) {
|
|
2077
|
+
if (failure3.kind !== "application") return failureStop(failure3);
|
|
2078
|
+
if (failure3.code === "ALREADY_OWNED") return alreadyPaid(sale, ref);
|
|
2079
|
+
if (failure3.code === "NOT_PENDING" || failure3.code === "NOT_ORDER_BUYER") forget(sale, sale.catalogRef);
|
|
2080
|
+
return stopWith("rejected", failure3.code === "NOT_PENDING" ? "orderClosed" : "paymentRefused");
|
|
2081
|
+
}
|
|
2082
|
+
function alreadyOwned(sale) {
|
|
2083
|
+
forget(sale, sale.catalogRef);
|
|
2084
|
+
return stopWith("rejected", "alreadyOwned");
|
|
2085
|
+
}
|
|
2086
|
+
function persist(sale, ref) {
|
|
2087
|
+
if (!sale.bridge.isCurrent()) return STALE2;
|
|
2088
|
+
return sale.partition.put(ref) ? ref : stopWith("rejected", "storageUnavailable");
|
|
2089
|
+
}
|
|
2090
|
+
function forget(sale, key) {
|
|
2091
|
+
return sale.bridge.isCurrent() && sale.partition.remove(key);
|
|
2092
|
+
}
|
|
2093
|
+
function contextResult(catalogRef, stop) {
|
|
2094
|
+
if (stop.failure) deps.report({ key: catalogRef, failure: stop.failure });
|
|
2095
|
+
return { status: stop.result };
|
|
2096
|
+
}
|
|
2097
|
+
async function submitProof(session, transaction) {
|
|
2098
|
+
const member = await memberOf(session);
|
|
2099
|
+
if (isStop(member)) return { status: member.result };
|
|
2100
|
+
if (!isCurrent(session)) return RETRYABLE;
|
|
2101
|
+
const completed = await deps.wixIap.completePayment(transaction);
|
|
2102
|
+
if (!isCurrent(session)) return RETRYABLE;
|
|
2103
|
+
const proof = { session, transaction, member, acknowledgement: acknowledgementOf(transaction, member.scopeKey) };
|
|
2104
|
+
return completed.status === "matched" ? matchedProof(proof, completed.payment) : refusedProof(proof, completed);
|
|
2105
|
+
}
|
|
2106
|
+
async function matchedProof(proof, payment) {
|
|
2107
|
+
const refs = proof.member.partition?.refs() ?? [];
|
|
2108
|
+
const ref = refs.find((stored) => stored.paymentId === payment.paymentId) ?? refs.find((stored) => stored.wixTransactionId === payment.wixTransactionId) ?? refByToken(refs, proof.transaction);
|
|
2109
|
+
if (ref?.storeProductId && ref.storeProductId !== proof.transaction.productId) return holdAssociation(ref);
|
|
2110
|
+
const tracking = trackingRef(ref, payment, proof.transaction, now());
|
|
2111
|
+
if (isFinalRefusal(payment.status)) return finalRefusal(proof, tracking);
|
|
2112
|
+
if (payment.status === "pending") return RETRYABLE;
|
|
2113
|
+
if (payment.status === "approved") {
|
|
2114
|
+
const granted = await accessGranted(proof, tracking);
|
|
2115
|
+
if (granted === "stale") return RETRYABLE;
|
|
2116
|
+
if (granted) return processed(proof, tracking);
|
|
254
2117
|
}
|
|
2118
|
+
return acceptTracking(proof, tracking, payment);
|
|
2119
|
+
}
|
|
2120
|
+
function holdAssociation(ref) {
|
|
2121
|
+
deps.report({ key: ref.key, failure: "invalidAssociation" });
|
|
2122
|
+
return { status: "rejected" };
|
|
2123
|
+
}
|
|
2124
|
+
async function accessGranted(proof, ref) {
|
|
2125
|
+
const adapter = adapterFor(ref.kind);
|
|
2126
|
+
if (!adapter || !ref.verticalRef) return false;
|
|
2127
|
+
const bridge = bindBridge(proof.session, deps.session, deps.bridge);
|
|
2128
|
+
const access = await adapter.readAccess(ref.verticalRef, bridge);
|
|
2129
|
+
if (!bridge.isCurrent()) return "stale";
|
|
2130
|
+
return access === "granted";
|
|
2131
|
+
}
|
|
2132
|
+
function processed(proof, ref) {
|
|
2133
|
+
proof.member.partition?.remove(ref.key);
|
|
2134
|
+
deps.report({ key: ref.key, access: "granted" });
|
|
2135
|
+
return { status: "processed", acknowledgement: proof.acknowledgement };
|
|
2136
|
+
}
|
|
2137
|
+
function acceptTracking(proof, ref, payment) {
|
|
2138
|
+
const partition = proof.member.partition;
|
|
2139
|
+
if (!partition || !isCurrent(proof.session) || !partition.put(ref)) return RETRYABLE;
|
|
2140
|
+
deps.report({ key: ref.key, access: !ref.kind && payment.status === "approved" ? "accessUnknown" : "processing" });
|
|
2141
|
+
return { status: "accepted", acknowledgement: proof.acknowledgement };
|
|
2142
|
+
}
|
|
2143
|
+
function finalRefusal(proof, ref) {
|
|
2144
|
+
const hold = ref ?? unassociatedHold(proof.transaction, now());
|
|
2145
|
+
const partition = proof.member.partition;
|
|
2146
|
+
const stored = { ...hold, stage: "needsSupport", appleTransactionId: proof.transaction.transactionId, updatedAt: now() };
|
|
2147
|
+
if (!partition || !isCurrent(proof.session) || !partition.put(stored)) return RETRYABLE;
|
|
2148
|
+
deps.report({ key: hold.key, access: "needsSupport" });
|
|
2149
|
+
return { status: "refusedFinal", acknowledgement: proof.acknowledgement };
|
|
2150
|
+
}
|
|
2151
|
+
function refusedProof(proof, failure3) {
|
|
2152
|
+
const refusal = failure3.kind === "application" ? COMPLETE_PAYMENT_REFUSALS[failure3.code] : failureStop(failure3).result;
|
|
2153
|
+
if (refusal === "refusedFinal") return finalRefusal(proof, startedRefFor(proof));
|
|
2154
|
+
if (refusal === "hold" || refusal === "rejected") return heldProof(proof, "rejected");
|
|
2155
|
+
return refusal === "ownershipConflict" ? heldProof(proof, "ownershipConflict") : { status: refusal };
|
|
2156
|
+
}
|
|
2157
|
+
function heldProof(proof, status) {
|
|
2158
|
+
const started = startedRefFor(proof);
|
|
2159
|
+
const hold = started && { ...started, stage: "held", appleTransactionId: proof.transaction.transactionId, updatedAt: now() };
|
|
2160
|
+
if (hold && !(isCurrent(proof.session) && proof.member.partition?.put(hold))) return RETRYABLE;
|
|
2161
|
+
deps.report({ key: started?.key ?? `payment:${proof.transaction.transactionId}`, failure: "paymentHeld" });
|
|
2162
|
+
return { status };
|
|
2163
|
+
}
|
|
2164
|
+
function startedRefFor(proof) {
|
|
2165
|
+
const refs = proof.member.partition?.refs() ?? [];
|
|
2166
|
+
const { productId, transactionId } = proof.transaction;
|
|
2167
|
+
const holding = refs.find((ref) => (ref.stage === "held" || ref.stage === "needsSupport") && ref.appleTransactionId === transactionId);
|
|
2168
|
+
if (holding) return holding;
|
|
2169
|
+
if (proof.transaction.appAccountToken) return refByToken(refs, proof.transaction);
|
|
2170
|
+
return refs.filter((ref) => ref.stage === "started" && ref.storeProductId === productId).reduce((newest, ref) => !newest || ref.updatedAt >= newest.updatedAt ? ref : newest, void 0);
|
|
255
2171
|
}
|
|
2172
|
+
async function getMemberContext(session) {
|
|
2173
|
+
const member = await memberOf(session);
|
|
2174
|
+
if (isStop(member)) return { status: member.result };
|
|
2175
|
+
return { status: "ok", context: { scopeKey: member.scopeKey } };
|
|
2176
|
+
}
|
|
2177
|
+
return { getPurchaseContext, getMemberContext, submitProof };
|
|
2178
|
+
}
|
|
2179
|
+
var RETRYABLE = { status: "retryable" };
|
|
2180
|
+
var STALE2 = { stop: true, result: "retryable" };
|
|
2181
|
+
function stopWith(result, failure3) {
|
|
2182
|
+
return { stop: true, result, failure: failure3 };
|
|
2183
|
+
}
|
|
2184
|
+
function retry(failure3) {
|
|
2185
|
+
return stopWith("retryable", failure3);
|
|
2186
|
+
}
|
|
2187
|
+
function isStop(value) {
|
|
2188
|
+
return typeof value === "object" && value !== null && value.stop === true;
|
|
2189
|
+
}
|
|
2190
|
+
function failureStop(failure3) {
|
|
2191
|
+
if (failure3.kind === "application") return stopWith("rejected", "notConfigured");
|
|
2192
|
+
return KIND_STOPS[failure3.kind];
|
|
2193
|
+
}
|
|
2194
|
+
var SIGN_IN = { stop: true, result: "authRequired", failure: "signInRequired" };
|
|
2195
|
+
var NOT_CONFIGURED = { stop: true, result: "rejected", failure: "notConfigured" };
|
|
2196
|
+
var UNAVAILABLE = { stop: true, result: "retryable", failure: "unavailable" };
|
|
2197
|
+
var KIND_STOPS = {
|
|
2198
|
+
authRequired: SIGN_IN,
|
|
2199
|
+
notPermitted: SIGN_IN,
|
|
2200
|
+
notEnabled: NOT_CONFIGURED,
|
|
2201
|
+
notProvisioned: NOT_CONFIGURED,
|
|
2202
|
+
environmentUnsupported: NOT_CONFIGURED,
|
|
2203
|
+
invalidArgument: NOT_CONFIGURED,
|
|
2204
|
+
sessionChanged: STALE2,
|
|
2205
|
+
unavailable: UNAVAILABLE,
|
|
2206
|
+
unrecognized: UNAVAILABLE,
|
|
2207
|
+
invalidResponse: UNAVAILABLE
|
|
2208
|
+
};
|
|
2209
|
+
function trackingRef(ref, payment, transaction, updatedAt) {
|
|
2210
|
+
const ids = { paymentId: payment.paymentId, wixTransactionId: payment.wixTransactionId, appleTransactionId: transaction.transactionId, updatedAt };
|
|
2211
|
+
if (ref) return { ...ref, ...ids, stage: "submitted" };
|
|
256
2212
|
return {
|
|
257
|
-
|
|
258
|
-
|
|
2213
|
+
key: `payment:${payment.paymentId}`,
|
|
2214
|
+
stage: "submitted",
|
|
2215
|
+
storeProductId: transaction.productId,
|
|
2216
|
+
...payment.appProductId ? { appProductId: payment.appProductId } : {},
|
|
2217
|
+
...ids
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function unassociatedHold(transaction, updatedAt) {
|
|
2221
|
+
return { key: `payment:${transaction.transactionId}`, stage: "needsSupport", storeProductId: transaction.productId, appleTransactionId: transaction.transactionId, updatedAt };
|
|
2222
|
+
}
|
|
2223
|
+
function acknowledgementOf(transaction, scopeKey) {
|
|
2224
|
+
return { transactionId: transaction.transactionId, productId: transaction.productId, environment: transaction.environment, scopeKey };
|
|
2225
|
+
}
|
|
2226
|
+
function refAt(partition, key) {
|
|
2227
|
+
return partition.refs().find((ref) => ref.key === key);
|
|
2228
|
+
}
|
|
2229
|
+
async function readMemberId(identity, session) {
|
|
2230
|
+
try {
|
|
2231
|
+
return await identity.memberId(session);
|
|
2232
|
+
} catch {
|
|
2233
|
+
return void 0;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
function refByToken(refs, transaction) {
|
|
2237
|
+
const token = transaction.appAccountToken?.toLowerCase();
|
|
2238
|
+
return token ? refs.find((ref) => ref.appAccountToken === token) : void 0;
|
|
2239
|
+
}
|
|
2240
|
+
async function readBinding(source) {
|
|
2241
|
+
try {
|
|
2242
|
+
return await source.current();
|
|
2243
|
+
} catch {
|
|
2244
|
+
return void 0;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// ../../stdlib/flows/iap-checkout/src/apple-iap-core.ts
|
|
2249
|
+
function createAppleIapCheckoutWith(deps) {
|
|
2250
|
+
return deps.enabled ? enabledCheckout(deps) : disabledCheckout();
|
|
2251
|
+
}
|
|
2252
|
+
var ASK_TO_BUY_HOLD_MS = 60 * 6e4;
|
|
2253
|
+
function enabledCheckout(deps) {
|
|
2254
|
+
const refs = deps.refs ?? createWorkerOrderRefStore();
|
|
2255
|
+
const bridge = deps.bridge ?? hostBridge() ?? MISSING_BRIDGE;
|
|
2256
|
+
const sales = /* @__PURE__ */ new Map();
|
|
2257
|
+
const payments = /* @__PURE__ */ new Map();
|
|
2258
|
+
const readGrants = /* @__PURE__ */ new Set();
|
|
2259
|
+
const accessReads = /* @__PURE__ */ new Map();
|
|
2260
|
+
let current = { item: null, failure: null, buying: false };
|
|
2261
|
+
let generation = deps.session.current().generation;
|
|
2262
|
+
let disposed = false;
|
|
2263
|
+
let tracking = Promise.resolve();
|
|
2264
|
+
let partitionRead = null;
|
|
2265
|
+
const awaitingApproval = /* @__PURE__ */ new Map();
|
|
2266
|
+
const backend = createWixOrderBackend({
|
|
2267
|
+
wixIap: deps.wixIap,
|
|
2268
|
+
identity: deps.identity,
|
|
2269
|
+
binding: deps.binding,
|
|
2270
|
+
session: deps.session,
|
|
2271
|
+
clock: deps.clock,
|
|
2272
|
+
cashier: deps.cashier,
|
|
2273
|
+
adapters: deps.adapters,
|
|
2274
|
+
refs,
|
|
2275
|
+
bridge,
|
|
2276
|
+
itemFor: (key) => sales.get(key),
|
|
2277
|
+
productBusy,
|
|
2278
|
+
report: onEvent
|
|
2279
|
+
});
|
|
2280
|
+
const coordinator = createPurchaseCoordinator({
|
|
2281
|
+
store: deps.store ?? appleManagedStore,
|
|
2282
|
+
backend,
|
|
2283
|
+
session: deps.session,
|
|
2284
|
+
clock: deps.clock,
|
|
2285
|
+
jitter: deps.jitter,
|
|
2286
|
+
onState: publish,
|
|
2287
|
+
onAwaitingApproval: reserveAskToBuy
|
|
2288
|
+
});
|
|
2289
|
+
async function start() {
|
|
2290
|
+
resetIfSessionChanged();
|
|
2291
|
+
await coordinator.start();
|
|
2292
|
+
await trackPayments({ dueOnly: false });
|
|
2293
|
+
return state();
|
|
2294
|
+
}
|
|
2295
|
+
async function checkAccess(items) {
|
|
2296
|
+
resetIfSessionChanged();
|
|
2297
|
+
const session = deps.session.current();
|
|
2298
|
+
if (session.kind !== "member") return state();
|
|
2299
|
+
for (const item of items) {
|
|
2300
|
+
const key = itemKey(item);
|
|
2301
|
+
const adapter = deps.adapters.find((candidate) => candidate.kind === item.reference.kind);
|
|
2302
|
+
if (!adapter?.currentAccess || payments.has(key) && payments.get(key).access !== "granted" && !readGrants.has(key)) continue;
|
|
2303
|
+
const seq = (accessReads.get(key) ?? 0) + 1;
|
|
2304
|
+
accessReads.set(key, seq);
|
|
2305
|
+
const access = await adapter.currentAccess(item, bindBridge(session, deps.session, bridge)).catch(() => "unavailable");
|
|
2306
|
+
if (deps.session.current().generation !== session.generation) break;
|
|
2307
|
+
if (accessReads.get(key) !== seq) continue;
|
|
2308
|
+
if (access === "granted") {
|
|
2309
|
+
setPayment(key, "granted");
|
|
2310
|
+
readGrants.add(key);
|
|
2311
|
+
} else if (access === "none" && readGrants.delete(key)) {
|
|
2312
|
+
payments.delete(key);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
return publishAndReturn();
|
|
2316
|
+
}
|
|
2317
|
+
async function buy(item) {
|
|
2318
|
+
resetIfSessionChanged();
|
|
2319
|
+
const key = itemKey(item);
|
|
2320
|
+
const appleProduct = settledAppleProduct(item);
|
|
2321
|
+
current = { item: key, failure: appleProduct ? null : "notAppleRoute", buying: false };
|
|
2322
|
+
if (!appleProduct) return publishAndReturn();
|
|
2323
|
+
sales.set(key, { item, appleProduct });
|
|
2324
|
+
current.buying = true;
|
|
2325
|
+
publish();
|
|
2326
|
+
await coordinator.purchase(key);
|
|
2327
|
+
if (current.item === key) current.buying = false;
|
|
2328
|
+
return publishAndReturn();
|
|
2329
|
+
}
|
|
2330
|
+
async function reconcile2() {
|
|
2331
|
+
resetIfSessionChanged();
|
|
2332
|
+
await coordinator.reconcile();
|
|
2333
|
+
await trackPayments({ dueOnly: false });
|
|
2334
|
+
return state();
|
|
2335
|
+
}
|
|
2336
|
+
async function retry2() {
|
|
2337
|
+
resetIfSessionChanged();
|
|
2338
|
+
await coordinator.retry();
|
|
2339
|
+
await trackPayments({ dueOnly: false });
|
|
2340
|
+
return state();
|
|
2341
|
+
}
|
|
2342
|
+
async function tick() {
|
|
2343
|
+
resetIfSessionChanged();
|
|
2344
|
+
await coordinator.tick();
|
|
2345
|
+
await trackPayments({ dueOnly: true });
|
|
2346
|
+
return state();
|
|
2347
|
+
}
|
|
2348
|
+
function state() {
|
|
2349
|
+
if (disposed || deps.session.current().generation !== generation) return blankState(true);
|
|
2350
|
+
const purchase = coordinator.getState();
|
|
2351
|
+
const progress = [...payments.values()].map(({ item, access, nextAttemptAt }) => ({ item, access, nextAttemptAt }));
|
|
2352
|
+
return {
|
|
2353
|
+
enabled: true,
|
|
2354
|
+
phase: phaseOf(purchase, progress),
|
|
2355
|
+
item: current.item,
|
|
2356
|
+
failure: current.failure,
|
|
2357
|
+
purchase,
|
|
2358
|
+
payments: progress,
|
|
2359
|
+
nextWakeAt: earliest([purchase.nextWakeAt, partitionRead?.nextAttemptAt ?? null, ...progress.map((payment) => payment.nextAttemptAt)])
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function dispose() {
|
|
2363
|
+
disposed = true;
|
|
2364
|
+
coordinator.dispose();
|
|
2365
|
+
}
|
|
2366
|
+
function settledAppleProduct(item) {
|
|
2367
|
+
return deps.pricing.view(item.reference).route === "appleIap" ? deps.pricing.appleProduct(item.reference) : null;
|
|
2368
|
+
}
|
|
2369
|
+
function onEvent(event) {
|
|
2370
|
+
resetIfSessionChanged();
|
|
2371
|
+
const owned = event.failure === "alreadyOwned";
|
|
2372
|
+
const access = owned ? "granted" : event.access;
|
|
2373
|
+
const failure3 = owned ? void 0 : event.failure;
|
|
2374
|
+
if (access) setPayment(event.key, access);
|
|
2375
|
+
if (owned) readGrants.add(event.key);
|
|
2376
|
+
if (labelOf(event.key) === current.item && (failure3 || access)) current.failure = failure3 ?? failureOfAccess(access);
|
|
2377
|
+
}
|
|
2378
|
+
function setPayment(key, access) {
|
|
2379
|
+
const due = access === "processing" ? deps.clock.now() + BASE_DELAY_MS : null;
|
|
2380
|
+
payments.set(key, { key, item: labelOf(key), access, attempts: 0, nextAttemptAt: due });
|
|
2381
|
+
}
|
|
2382
|
+
function phaseOf(purchase, progress) {
|
|
2383
|
+
const mine = progress.find((payment) => payment.item === current.item);
|
|
2384
|
+
if (mine?.access === "granted") return "accessReady";
|
|
2385
|
+
if (current.failure) return "refused";
|
|
2386
|
+
if (OVERLAY_PHASES.has(purchase.phase)) return purchase.phase;
|
|
2387
|
+
if (current.buying) return "preparing";
|
|
2388
|
+
if (mine?.access === "processing" || purchase.items.some(isInFlight)) return "processing";
|
|
2389
|
+
if (!current.item && progress.some((payment) => payment.access === "processing")) return "processing";
|
|
2390
|
+
return "idle";
|
|
2391
|
+
}
|
|
2392
|
+
function reserveAskToBuy(productId) {
|
|
2393
|
+
awaitingApproval.set(productId, { items: itemsFor(productId).length, since: deps.clock.now() });
|
|
2394
|
+
}
|
|
2395
|
+
function productBusy(productId) {
|
|
2396
|
+
const items = itemsFor(productId);
|
|
2397
|
+
const pending = awaitingApproval.get(productId);
|
|
2398
|
+
if (pending && (items.length > pending.items || deps.clock.now() - pending.since >= ASK_TO_BUY_HOLD_MS)) {
|
|
2399
|
+
awaitingApproval.delete(productId);
|
|
2400
|
+
}
|
|
2401
|
+
return awaitingApproval.has(productId) || items.some(isUnfinished);
|
|
2402
|
+
}
|
|
2403
|
+
function itemsFor(productId) {
|
|
2404
|
+
return coordinator.getState().items.filter((held) => held.productId === productId);
|
|
2405
|
+
}
|
|
2406
|
+
function resetIfSessionChanged() {
|
|
2407
|
+
const next = deps.session.current().generation;
|
|
2408
|
+
if (next === generation) return;
|
|
2409
|
+
generation = next;
|
|
2410
|
+
sales.clear();
|
|
2411
|
+
payments.clear();
|
|
2412
|
+
readGrants.clear();
|
|
2413
|
+
awaitingApproval.clear();
|
|
2414
|
+
partitionRead = null;
|
|
2415
|
+
current = { item: null, failure: null, buying: false };
|
|
2416
|
+
}
|
|
2417
|
+
function publish() {
|
|
2418
|
+
if (!disposed && deps.session.current().generation === generation) deps.onState?.(state());
|
|
2419
|
+
}
|
|
2420
|
+
function publishAndReturn() {
|
|
2421
|
+
publish();
|
|
2422
|
+
return state();
|
|
2423
|
+
}
|
|
2424
|
+
function trackPayments(options) {
|
|
2425
|
+
const run = tracking.then(() => trackOnce(options));
|
|
2426
|
+
tracking = run.catch(() => void 0);
|
|
2427
|
+
return run;
|
|
2428
|
+
}
|
|
2429
|
+
async function trackOnce(options) {
|
|
2430
|
+
resetIfSessionChanged();
|
|
2431
|
+
const session = deps.session.current();
|
|
2432
|
+
if (session.kind !== "member" || options.dueOnly && !anyDue()) return;
|
|
2433
|
+
if (!options.dueOnly) partitionRead = null;
|
|
2434
|
+
const tracked = await trackedPartition(session);
|
|
2435
|
+
if (!isCurrent(session)) return;
|
|
2436
|
+
if (tracked === "none") return;
|
|
2437
|
+
if (!tracked) return backOffDue();
|
|
2438
|
+
partitionRead = null;
|
|
2439
|
+
for (const key of pruneExpiredHolds(tracked.partition, deps.clock.now())) payments.delete(key);
|
|
2440
|
+
for (const ref of tracked.partition.refs()) {
|
|
2441
|
+
if (!isCurrent(session)) return;
|
|
2442
|
+
if (ref.stage === "needsSupport") setPayment(ref.key, "needsSupport");
|
|
2443
|
+
if (ref.stage !== "submitted" || !ref.paymentId) continue;
|
|
2444
|
+
if (!options.dueOnly) restartBudget(ref.key);
|
|
2445
|
+
if (isDue(ref.key, options.dueOnly)) await trackOne(session, tracked.partition, ref);
|
|
2446
|
+
}
|
|
2447
|
+
publish();
|
|
2448
|
+
}
|
|
2449
|
+
async function trackedPartition(session) {
|
|
2450
|
+
const binding = await bindingOf(deps.binding);
|
|
2451
|
+
if (!binding || !isCurrent(session)) return binding === null ? "none" : null;
|
|
2452
|
+
const stored = refs.hasRefs(binding);
|
|
2453
|
+
if (stored !== true) return stored === false ? "none" : null;
|
|
2454
|
+
const memberId = await memberIdOf(session);
|
|
2455
|
+
if (!memberId || !isCurrent(session)) return null;
|
|
2456
|
+
const partition = refs.open({ purchasesAppId: binding.purchasesAppId, siteId: binding.siteId, memberId });
|
|
2457
|
+
return partition && { partition };
|
|
2458
|
+
}
|
|
2459
|
+
async function memberIdOf(session) {
|
|
2460
|
+
try {
|
|
2461
|
+
return await deps.identity.memberId(session);
|
|
2462
|
+
} catch {
|
|
2463
|
+
return null;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
function backOffDue() {
|
|
2467
|
+
const attempts = (partitionRead?.attempts ?? 0) + 1;
|
|
2468
|
+
partitionRead = { attempts, nextAttemptAt: attempts >= MAX_AUTOMATIC_ATTEMPTS ? null : deps.clock.now() + backoffDelay(attempts, deps.jitter) };
|
|
2469
|
+
for (const payment of payments.values()) {
|
|
2470
|
+
if (payment.access !== "processing" || !isDue(payment.key, true)) continue;
|
|
2471
|
+
const attempts2 = payment.attempts + 1;
|
|
2472
|
+
const nextAttemptAt = attempts2 >= MAX_AUTOMATIC_ATTEMPTS ? null : deps.clock.now() + backoffDelay(attempts2, deps.jitter);
|
|
2473
|
+
Object.assign(payment, { attempts: attempts2, nextAttemptAt });
|
|
2474
|
+
}
|
|
2475
|
+
publish();
|
|
2476
|
+
}
|
|
2477
|
+
function anyDue() {
|
|
2478
|
+
const readDue = partitionRead?.nextAttemptAt != null && partitionRead.nextAttemptAt <= deps.clock.now();
|
|
2479
|
+
return readDue || [...payments.values()].some((payment) => payment.access === "processing" && isDue(payment.key, true));
|
|
2480
|
+
}
|
|
2481
|
+
async function trackOne(session, partition, ref) {
|
|
2482
|
+
const read2 = await deps.wixIap.getPayment({ paymentId: ref.paymentId ?? "" });
|
|
2483
|
+
if (!isCurrent(session)) return;
|
|
2484
|
+
if (read2.status !== "found") return read2.kind === "application" ? markNeedsSupport(partition, ref) : backOff(partition, ref, false);
|
|
2485
|
+
await settlePayment(session, partition, ref, read2.payment);
|
|
2486
|
+
}
|
|
2487
|
+
async function settlePayment(session, partition, ref, payment) {
|
|
2488
|
+
if (isFinalRefusal(payment.status)) return markNeedsSupport(partition, ref);
|
|
2489
|
+
if (payment.status !== "approved") return backOff(partition, ref, false);
|
|
2490
|
+
if (!ref.kind) return settleUnassociated(partition, ref);
|
|
2491
|
+
const access = await accessOf2(session, ref);
|
|
2492
|
+
if (!isCurrent(session)) return;
|
|
2493
|
+
if (access === "granted") return grant(partition, ref);
|
|
2494
|
+
backOff(partition, ref, access === "none");
|
|
2495
|
+
}
|
|
2496
|
+
function grant(partition, ref) {
|
|
2497
|
+
partition.remove(ref.key);
|
|
2498
|
+
setPayment(ref.key, "granted");
|
|
2499
|
+
}
|
|
2500
|
+
function settleUnassociated(partition, ref) {
|
|
2501
|
+
partition.remove(ref.key);
|
|
2502
|
+
setPayment(ref.key, "accessUnknown");
|
|
2503
|
+
}
|
|
2504
|
+
async function accessOf2(session, ref) {
|
|
2505
|
+
const adapter = deps.adapters.find((candidate) => candidate.kind === ref.kind);
|
|
2506
|
+
if (!adapter || !ref.verticalRef) return "unavailable";
|
|
2507
|
+
return adapter.readAccess(ref.verticalRef, bindBridge(session, deps.session, bridge));
|
|
2508
|
+
}
|
|
2509
|
+
function markNeedsSupport(partition, ref) {
|
|
2510
|
+
partition.put({ ...ref, stage: "needsSupport", updatedAt: deps.clock.now() });
|
|
2511
|
+
setPayment(ref.key, "needsSupport");
|
|
2512
|
+
if (labelOf(ref.key) === current.item) current.failure = "needsSupport";
|
|
2513
|
+
}
|
|
2514
|
+
function backOff(partition, ref, approvedWithoutAccess) {
|
|
2515
|
+
const payment = payments.get(ref.key) ?? newTracked(ref.key);
|
|
2516
|
+
const attempts = payment.attempts + 1;
|
|
2517
|
+
const exhausted = attempts >= MAX_AUTOMATIC_ATTEMPTS;
|
|
2518
|
+
if (exhausted && approvedWithoutAccess) return markNeedsSupport(partition, ref);
|
|
2519
|
+
const nextAttemptAt = exhausted ? null : deps.clock.now() + backoffDelay(attempts, deps.jitter);
|
|
2520
|
+
payments.set(ref.key, { ...payment, access: "processing", attempts, nextAttemptAt });
|
|
2521
|
+
}
|
|
2522
|
+
function restartBudget(key) {
|
|
2523
|
+
const payment = payments.get(key);
|
|
2524
|
+
if (payment?.access === "processing") Object.assign(payment, { attempts: 0, nextAttemptAt: deps.clock.now() });
|
|
2525
|
+
}
|
|
2526
|
+
function isDue(key, dueOnly) {
|
|
2527
|
+
const nextAttemptAt = payments.get(key)?.nextAttemptAt;
|
|
2528
|
+
return !dueOnly || nextAttemptAt === void 0 || nextAttemptAt !== null && nextAttemptAt <= deps.clock.now();
|
|
2529
|
+
}
|
|
2530
|
+
function newTracked(key) {
|
|
2531
|
+
return { key, item: labelOf(key), access: "processing", attempts: 0, nextAttemptAt: null };
|
|
2532
|
+
}
|
|
2533
|
+
function isCurrent(session) {
|
|
2534
|
+
return !disposed && deps.session.current().generation === session.generation;
|
|
2535
|
+
}
|
|
2536
|
+
return { start, buy, checkAccess, reconcile: reconcile2, retry: retry2, tick, state, dispose };
|
|
2537
|
+
}
|
|
2538
|
+
function notEnabledState() {
|
|
2539
|
+
return blankState(false);
|
|
2540
|
+
}
|
|
2541
|
+
function disabledCheckout() {
|
|
2542
|
+
const state = () => blankState(false);
|
|
2543
|
+
const resolved = () => Promise.resolve(state());
|
|
2544
|
+
return { start: resolved, buy: resolved, checkAccess: resolved, reconcile: resolved, retry: resolved, tick: resolved, state, dispose: () => {
|
|
2545
|
+
} };
|
|
2546
|
+
}
|
|
2547
|
+
function blankState(enabled) {
|
|
2548
|
+
return {
|
|
2549
|
+
enabled,
|
|
2550
|
+
phase: enabled ? "idle" : "notEnabled",
|
|
2551
|
+
item: null,
|
|
2552
|
+
failure: null,
|
|
2553
|
+
payments: [],
|
|
2554
|
+
nextWakeAt: null,
|
|
2555
|
+
purchase: { phase: "idle", errorCode: null, items: [], unfinishedCount: null, nextWakeAt: null }
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
function isUnfinished(item) {
|
|
2559
|
+
return item.completion === "pending" || item.completion === "retryRequired";
|
|
2560
|
+
}
|
|
2561
|
+
function isInFlight(item) {
|
|
2562
|
+
if (item.completion === "finished") return false;
|
|
2563
|
+
return item.access === "processing" || item.access === "confirming" || item.access === "pending" || item.access === "waitingToRetry";
|
|
2564
|
+
}
|
|
2565
|
+
function itemKey(item) {
|
|
2566
|
+
return `${item.reference.kind}:${item.reference.id}`;
|
|
2567
|
+
}
|
|
2568
|
+
function labelOf(key) {
|
|
2569
|
+
return key.startsWith("payment:") ? "payment" : key;
|
|
2570
|
+
}
|
|
2571
|
+
function backoffDelay(attempts, jitter) {
|
|
2572
|
+
const base = Math.min(BASE_DELAY_MS * 2 ** (attempts - 1), MAX_RETRY_DELAY_MS);
|
|
2573
|
+
return base + Math.max(0, jitter?.(base) ?? 0);
|
|
2574
|
+
}
|
|
2575
|
+
function earliest(times) {
|
|
2576
|
+
const due = times.filter((time) => time !== null);
|
|
2577
|
+
return due.length > 0 ? Math.min(...due) : null;
|
|
2578
|
+
}
|
|
2579
|
+
async function bindingOf(source) {
|
|
2580
|
+
try {
|
|
2581
|
+
return await source.current();
|
|
2582
|
+
} catch {
|
|
2583
|
+
return void 0;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
function failureOfAccess(access) {
|
|
2587
|
+
return access === "needsSupport" ? "needsSupport" : null;
|
|
2588
|
+
}
|
|
2589
|
+
var BASE_DELAY_MS = 1e3;
|
|
2590
|
+
var OVERLAY_PHASES = /* @__PURE__ */ new Set(["purchasing", "awaitingApproval", "cancelled", "signInRequired", "unavailable"]);
|
|
2591
|
+
|
|
2592
|
+
// ../../stdlib/flows/iap-checkout/src/purchase-fields.ts
|
|
2593
|
+
function iapPurchaseFields(view, state, readiness, copy) {
|
|
2594
|
+
const key = `${view.reference.kind}:${view.reference.id}`;
|
|
2595
|
+
if (view.route !== "appleIap" && !holdsSale(state, key)) return { ...action(copy, "enabled", ""), appleRoute: false };
|
|
2596
|
+
if (readiness.status !== "ready") return action(copy, "disabled", readiness.status === "checking" ? "" : copy.unavailable);
|
|
2597
|
+
const own = state.item === key;
|
|
2598
|
+
const payment = state.payments.find((tracked) => tracked.item === key)?.access;
|
|
2599
|
+
if (own && state.phase === "accessReady" || payment === "granted") return { ...action(copy, "disabled", ""), accessReady: true };
|
|
2600
|
+
if (IN_FLIGHT.has(state.phase)) return action(copy, "busy", own ? copy.processing : "");
|
|
2601
|
+
if (own) return outcomeAction(state, copy);
|
|
2602
|
+
if (payment === "processing") return action(copy, "busy", copy.processing);
|
|
2603
|
+
if (payment === "needsSupport") return action(copy, "disabled", copy.support);
|
|
2604
|
+
return action(copy, "enabled", "");
|
|
2605
|
+
}
|
|
2606
|
+
function holdsSale(state, key) {
|
|
2607
|
+
if (state.payments.some((tracked) => tracked.item === key)) return true;
|
|
2608
|
+
return state.item === key && (SALE_PHASES.has(state.phase) || state.purchase.phase === "awaitingApproval");
|
|
2609
|
+
}
|
|
2610
|
+
function outcomeAction(state, copy) {
|
|
2611
|
+
if (state.purchase.phase === "awaitingApproval") return action(copy, "disabled", copy.awaitingApproval);
|
|
2612
|
+
switch (state.phase) {
|
|
2613
|
+
case "awaitingApproval":
|
|
2614
|
+
return action(copy, "disabled", copy.awaitingApproval);
|
|
2615
|
+
case "signInRequired":
|
|
2616
|
+
return action(copy, "disabled", copy.signInRequired);
|
|
2617
|
+
case "unavailable":
|
|
2618
|
+
return action(copy, "enabled", copy.retry);
|
|
2619
|
+
case "refused":
|
|
2620
|
+
return refusalAction(state.failure, copy);
|
|
2621
|
+
case "notEnabled":
|
|
2622
|
+
return action(copy, "disabled", copy.unavailable);
|
|
2623
|
+
default:
|
|
2624
|
+
return action(copy, "enabled", "");
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
function refusalAction(failure3, copy) {
|
|
2628
|
+
const [mode, note] = REFUSALS[failure3 ?? "unavailable"] ?? ["enabled", "retry"];
|
|
2629
|
+
return action(copy, mode, note ? copy[note] : "");
|
|
2630
|
+
}
|
|
2631
|
+
function action(copy, mode, note) {
|
|
2632
|
+
return { appleRoute: true, accessReady: false, buyLabel: copy.buy, buyDisabled: mode !== "enabled", buyBusy: mode === "busy", buyNote: note };
|
|
2633
|
+
}
|
|
2634
|
+
var IN_FLIGHT = /* @__PURE__ */ new Set(["preparing", "purchasing", "processing"]);
|
|
2635
|
+
var SALE_PHASES = /* @__PURE__ */ new Set([...IN_FLIGHT, "awaitingApproval", "accessReady"]);
|
|
2636
|
+
var REFUSALS = {
|
|
2637
|
+
productChanged: ["enabled", "priceChanged"],
|
|
2638
|
+
paymentHeld: ["disabled", "support"],
|
|
2639
|
+
needsSupport: ["disabled", "support"],
|
|
2640
|
+
invalidAssociation: ["disabled", "support"],
|
|
2641
|
+
// Nothing in the checkout clears this, so the action stays tappable: a tap re-reads the order
|
|
2642
|
+
// (never a second charge) and turns into access once the vertical grants it.
|
|
2643
|
+
paymentInProgress: ["enabled", "processing"],
|
|
2644
|
+
approvalRequired: ["disabled", "awaitingApproval"],
|
|
2645
|
+
signInRequired: ["disabled", "signInRequired"],
|
|
2646
|
+
notConfigured: ["disabled", "unavailable"],
|
|
2647
|
+
notEnabled: ["disabled", "unavailable"],
|
|
2648
|
+
storageUnavailable: ["disabled", "unavailable"],
|
|
2649
|
+
unsupportedItem: ["disabled", "unavailable"],
|
|
2650
|
+
// The action shows only on an App Store route, so a refusal for any other route is stale.
|
|
2651
|
+
notAppleRoute: ["enabled", null]
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
// ../../stdlib/flows/iap-checkout/src/commerce-core.ts
|
|
2655
|
+
function createIapCommerceWith(deps) {
|
|
2656
|
+
let readiness = { status: "checking" };
|
|
2657
|
+
let checkout;
|
|
2658
|
+
let opening;
|
|
2659
|
+
let disposed = false;
|
|
2660
|
+
let armedInterval = null;
|
|
2661
|
+
let lastPollAt = null;
|
|
2662
|
+
let lastPricedAt = null;
|
|
2663
|
+
let pricedItems = null;
|
|
2664
|
+
let lastScopeComplete = true;
|
|
2665
|
+
let stopWatchingSession;
|
|
2666
|
+
let stopWatchingAccess;
|
|
2667
|
+
let checkoutStarted = false;
|
|
2668
|
+
let recheckAfterOpening = false;
|
|
2669
|
+
async function loadPrices(items) {
|
|
2670
|
+
if (!deps.pricing.enabled) return { scopeComplete: true };
|
|
2671
|
+
lastPricedAt = deps.clock.now();
|
|
2672
|
+
pricedItems = items;
|
|
2673
|
+
const [identity, read2] = await Promise.all([deps.host.identity(), deps.binding.current().then((binding) => ({ binding }), () => null)]);
|
|
2674
|
+
const scope = read2 ? catalogScope(identity, read2.binding) : { appId: "", bundleId: "" };
|
|
2675
|
+
await deps.pricing.load({ scope, platform: identity?.platform ?? "", items });
|
|
2676
|
+
lastScopeComplete = identity !== null && read2 !== null;
|
|
2677
|
+
if (checkout) await checkAccess(checkout);
|
|
2678
|
+
return { scopeComplete: lastScopeComplete };
|
|
2679
|
+
}
|
|
2680
|
+
function pollDirective(action2) {
|
|
2681
|
+
const interval = checkout?.state().nextWakeAt != null ? DUE_WORK_POLL_MS : deps.pricing.enabled || checkout ? PRICE_REFRESH_MS : null;
|
|
2682
|
+
if (interval === null || interval === armedInterval) return void 0;
|
|
2683
|
+
armedInterval = interval;
|
|
2684
|
+
lastPollAt ?? (lastPollAt = deps.clock.now());
|
|
2685
|
+
return { id: POLL_ID2, action: action2, intervalMs: interval, immediate: true };
|
|
2686
|
+
}
|
|
2687
|
+
async function poll() {
|
|
2688
|
+
const now = deps.clock.now();
|
|
2689
|
+
const suspended = lastPollAt !== null && now - lastPollAt > SUSPENDED_AFTER_MS;
|
|
2690
|
+
lastPollAt = now;
|
|
2691
|
+
await (suspended ? reconcile2() : checkout ? checkout.tick() : startedCheckout());
|
|
2692
|
+
if (pricedItems && lastPricedAt !== null && now - lastPricedAt >= PRICE_REFRESH_MS - PRICE_REFRESH_SLACK_MS) await loadPrices(pricedItems);
|
|
2693
|
+
}
|
|
2694
|
+
function reconcile2() {
|
|
2695
|
+
return recover((opened) => opened.reconcile());
|
|
2696
|
+
}
|
|
2697
|
+
function start() {
|
|
2698
|
+
return recover((opened) => opened.start());
|
|
2699
|
+
}
|
|
2700
|
+
async function buy(item) {
|
|
2701
|
+
if (unanswered2(deps.pricing.view(item.reference))) return state();
|
|
2702
|
+
const started = await startedCheckout();
|
|
2703
|
+
return started ? started.opened.buy(item) : state();
|
|
2704
|
+
}
|
|
2705
|
+
function purchaseFields(reference, copy) {
|
|
2706
|
+
const view = deps.pricing.view(reference);
|
|
2707
|
+
const fields = iapPurchaseFields({ ...view, route: deps.pricing.continueCheckoutGuard(reference).route }, state(), readiness, copy);
|
|
2708
|
+
if (!fields.appleRoute || fields.accessReady || fields.buyDisabled) return fields;
|
|
2709
|
+
if (deps.session.current().kind !== "member") return { ...fields, buyDisabled: true, buyNote: copy.signInRequired };
|
|
2710
|
+
if (!unanswered2(view)) return fields;
|
|
2711
|
+
return { ...fields, buyDisabled: true, buyNote: view.reason === "mappingUnavailable" ? copy.unavailable : fields.buyNote };
|
|
2712
|
+
}
|
|
2713
|
+
function catalogScope(identity, binding) {
|
|
2714
|
+
const siteId = binding?.siteId ?? (identity?.platform === "web" ? "" : deps.host.preferenceSite());
|
|
2715
|
+
return { appId: identity?.appId ?? "", bundleId: identity?.bundleId ?? "", ...siteId ? { siteId } : {} };
|
|
2716
|
+
}
|
|
2717
|
+
function open2() {
|
|
2718
|
+
if (checkout || disposed) return Promise.resolve(checkout);
|
|
2719
|
+
return opening ?? (opening = openWhenReady().finally(() => {
|
|
2720
|
+
opening = void 0;
|
|
2721
|
+
if (!recheckAfterOpening) return;
|
|
2722
|
+
recheckAfterOpening = false;
|
|
2723
|
+
if (!checkout && !disposed) void startedCheckout();
|
|
2724
|
+
}));
|
|
2725
|
+
}
|
|
2726
|
+
async function openWhenReady() {
|
|
2727
|
+
const staticGaps = configurationGaps();
|
|
2728
|
+
if (staticGaps.length > 0) return settle({ status: "unavailable", gaps: staticGaps });
|
|
2729
|
+
const routes = deps.routes;
|
|
2730
|
+
const [identity, binding] = await Promise.all([deps.host.identity(), readBinding2()]);
|
|
2731
|
+
const host = identity && binding && hostFor(identity, binding);
|
|
2732
|
+
const gaps = await hostGaps(host, binding);
|
|
2733
|
+
if (!host || gaps.length > 0 || disposed) {
|
|
2734
|
+
watchSessionUntilReady();
|
|
2735
|
+
return settle({ status: "unavailable", gaps });
|
|
2736
|
+
}
|
|
2737
|
+
stopWatchingSession?.();
|
|
2738
|
+
stopWatchingSession = void 0;
|
|
2739
|
+
checkout = createAppleIapCheckoutWith({
|
|
2740
|
+
enabled: true,
|
|
2741
|
+
wixIap: deps.wixIapClient({ host, route: routes.wixIap }),
|
|
2742
|
+
binding: deps.binding,
|
|
2743
|
+
session: deps.session,
|
|
2744
|
+
identity: deps.identity,
|
|
2745
|
+
clock: deps.clock,
|
|
2746
|
+
cashier: routes.cashier,
|
|
2747
|
+
adapters: deps.adapters,
|
|
2748
|
+
pricing: deps.pricing,
|
|
2749
|
+
store: deps.store,
|
|
2750
|
+
refs: deps.refs,
|
|
2751
|
+
bridge: deps.bridge,
|
|
2752
|
+
jitter: deps.jitter,
|
|
2753
|
+
onState: () => deps.onChange?.()
|
|
2754
|
+
});
|
|
2755
|
+
return settle({ status: "ready" });
|
|
2756
|
+
}
|
|
2757
|
+
function watchSessionUntilReady() {
|
|
2758
|
+
stopWatchingSession ?? (stopWatchingSession = deps.session.subscribe(() => {
|
|
2759
|
+
if (checkout || disposed) return;
|
|
2760
|
+
if (opening) recheckAfterOpening = true;
|
|
2761
|
+
else void startedCheckout();
|
|
2762
|
+
}));
|
|
2763
|
+
}
|
|
2764
|
+
function configurationGaps() {
|
|
2765
|
+
return [...deps.routes ? [] : ["routes"], ...deps.pricing.enabled ? [] : ["mapping"]];
|
|
2766
|
+
}
|
|
2767
|
+
async function hostGaps(host, binding) {
|
|
2768
|
+
if (!host) return ["binding"];
|
|
2769
|
+
if (!fencedTo(binding)) return ["nativeIdentity"];
|
|
2770
|
+
const gaps = [];
|
|
2771
|
+
if (!await managedStoreEnabled()) gaps.push("managedStore");
|
|
2772
|
+
if (binding && deps.refs.hasRefs(binding) === null) gaps.push("storage");
|
|
2773
|
+
return gaps;
|
|
2774
|
+
}
|
|
2775
|
+
function fencedTo(binding) {
|
|
2776
|
+
const site = deps.session.site?.();
|
|
2777
|
+
return deps.session.identityBound === true && !!binding && !!site && site.toLowerCase() === binding.siteId.toLowerCase();
|
|
2778
|
+
}
|
|
2779
|
+
async function managedStoreEnabled() {
|
|
2780
|
+
const unfinished = await deps.store.getUnfinishedTransactions().catch(() => null);
|
|
2781
|
+
return unfinished?.status === "ok";
|
|
2782
|
+
}
|
|
2783
|
+
function settle(next) {
|
|
2784
|
+
const changed = JSON.stringify(next) !== JSON.stringify(readiness);
|
|
2785
|
+
readiness = next;
|
|
2786
|
+
if (changed) deps.onChange?.();
|
|
2787
|
+
return checkout;
|
|
2788
|
+
}
|
|
2789
|
+
function readBinding2() {
|
|
2790
|
+
return deps.binding.current().catch(() => null);
|
|
2791
|
+
}
|
|
2792
|
+
async function startedCheckout() {
|
|
2793
|
+
if (deps.adapters.length === 0) return void 0;
|
|
2794
|
+
const opened = await open2();
|
|
2795
|
+
if (!opened) return void 0;
|
|
2796
|
+
if (checkoutStarted) return { opened, fresh: false };
|
|
2797
|
+
checkoutStarted = true;
|
|
2798
|
+
await opened.start();
|
|
2799
|
+
stopWatchingAccess = deps.session.subscribe(() => {
|
|
2800
|
+
if (!disposed) void checkAccess(opened);
|
|
2801
|
+
});
|
|
2802
|
+
await checkAccess(opened);
|
|
2803
|
+
return { opened, fresh: true };
|
|
2804
|
+
}
|
|
2805
|
+
async function recover(run) {
|
|
2806
|
+
const started = await startedCheckout();
|
|
2807
|
+
if (!started) return state();
|
|
2808
|
+
if (started.fresh) return state();
|
|
2809
|
+
await run(started.opened);
|
|
2810
|
+
return checkAccess(started.opened);
|
|
2811
|
+
}
|
|
2812
|
+
function checkAccess(opened) {
|
|
2813
|
+
const items = (pricedItems ?? []).filter((item) => deps.pricing.view(item.reference).route === "appleIap");
|
|
2814
|
+
return items.length ? opened.checkAccess(items.map((item) => ({ reference: item.reference }))) : Promise.resolve(state());
|
|
2815
|
+
}
|
|
2816
|
+
function state() {
|
|
2817
|
+
return checkout?.state() ?? notEnabledState();
|
|
2818
|
+
}
|
|
2819
|
+
return {
|
|
2820
|
+
pricing: deps.pricing,
|
|
2821
|
+
loadPrices,
|
|
2822
|
+
readiness: () => readiness,
|
|
2823
|
+
scopeComplete: () => lastScopeComplete,
|
|
2824
|
+
state,
|
|
2825
|
+
purchaseFields,
|
|
2826
|
+
start,
|
|
2827
|
+
buy,
|
|
2828
|
+
reconcile: reconcile2,
|
|
2829
|
+
tick: () => checkout ? checkout.tick() : Promise.resolve(state()),
|
|
2830
|
+
pollDirective,
|
|
2831
|
+
poll,
|
|
2832
|
+
dispose: () => {
|
|
2833
|
+
disposed = true;
|
|
2834
|
+
stopWatchingSession?.();
|
|
2835
|
+
stopWatchingAccess?.();
|
|
2836
|
+
deps.pricing.dispose();
|
|
2837
|
+
checkout?.dispose();
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
var POLL_ID2 = "iapCommerce";
|
|
2842
|
+
var PRICE_REFRESH_MS = 6e4;
|
|
2843
|
+
var PRICE_REFRESH_SLACK_MS = 1e4;
|
|
2844
|
+
var DUE_WORK_POLL_MS = 2e3;
|
|
2845
|
+
var SUSPENDED_AFTER_MS = 2 * PRICE_REFRESH_MS;
|
|
2846
|
+
function unanswered2(view) {
|
|
2847
|
+
return view.route === "resolving" || view.reason === "mappingUnavailable";
|
|
2848
|
+
}
|
|
2849
|
+
function hostFor(identity, binding) {
|
|
2850
|
+
if (binding.himalayaAppId !== identity.appId || binding.bundleId !== identity.bundleId) return null;
|
|
2851
|
+
return { appId: identity.appId, bundleId: identity.bundleId, siteId: binding.siteId };
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
// ../../stdlib/flows/iap-checkout/src/commerce.ts
|
|
2855
|
+
function createIapCommerce(options = {}) {
|
|
2856
|
+
const session = hostSessionFromGlobal();
|
|
2857
|
+
const identity = memberIdentityOf(session);
|
|
2858
|
+
const binding = sharedHostAppBindingSource();
|
|
2859
|
+
return createIapCommerceWith({
|
|
2860
|
+
routes: appleIapCheckoutRoutes(),
|
|
2861
|
+
pricing: createIapCatalogPricing({ onChange: options.onChange }),
|
|
2862
|
+
binding,
|
|
2863
|
+
session,
|
|
2864
|
+
host: hostOfWorker(),
|
|
2865
|
+
store: appleManagedStore,
|
|
2866
|
+
refs: createWorkerOrderRefStore(),
|
|
2867
|
+
clock: { now: () => Date.now() },
|
|
2868
|
+
adapters: options.adapters ?? [],
|
|
2869
|
+
identity,
|
|
2870
|
+
wixIapClient: ({ host, route }) => createWixIapClient({ session, binding, identity, host, route }),
|
|
2871
|
+
onChange: options.onChange
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
function hostOfWorker() {
|
|
2875
|
+
let read2;
|
|
2876
|
+
return {
|
|
2877
|
+
identity: () => read2 ?? (read2 = readIdentity().then((identity) => {
|
|
2878
|
+
if (!identity) read2 = void 0;
|
|
2879
|
+
return identity;
|
|
2880
|
+
})),
|
|
2881
|
+
preferenceSite
|
|
259
2882
|
};
|
|
260
2883
|
}
|
|
2884
|
+
async function readIdentity() {
|
|
2885
|
+
const info = await get().catch(() => void 0);
|
|
2886
|
+
return info?.appId && info.bundleId && info.platform ? { appId: info.appId, bundleId: info.bundleId, platform: info.platform } : null;
|
|
2887
|
+
}
|
|
2888
|
+
function preferenceSite() {
|
|
2889
|
+
const value = globalThis.himalaya?.input?.__himiPreferenceBusiness;
|
|
2890
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// ../../stdlib/flows/iap-checkout/src/iap-verticals/shared.ts
|
|
2894
|
+
function singleUnitOf(kind, item) {
|
|
2895
|
+
const quantity = item.quantity ?? 1;
|
|
2896
|
+
if (item.reference.kind !== kind || !isNonEmpty3(item.reference.id) || quantity !== 1) return UNSUPPORTED;
|
|
2897
|
+
return { eligible: true };
|
|
2898
|
+
}
|
|
2899
|
+
function attemptRefused(refusal) {
|
|
2900
|
+
if (refusal.status === "sessionChanged" || refusal.status === "unavailable") return { status: refusal.status };
|
|
2901
|
+
if (refusal.status === "notEnabled") return { status: "refused", reason: "notConfigured" };
|
|
2902
|
+
return { status: "refused", reason: "orderRefused" };
|
|
2903
|
+
}
|
|
2904
|
+
function accessRefused(refusal) {
|
|
2905
|
+
if (refusal.status === "sessionChanged") return "sessionChanged";
|
|
2906
|
+
return refusal.status === "http" && refusal.httpStatus === 404 ? "none" : "unavailable";
|
|
2907
|
+
}
|
|
2908
|
+
function isRefusal(outcome) {
|
|
2909
|
+
return outcome.status !== "ok";
|
|
2910
|
+
}
|
|
2911
|
+
function bodyObject(outcome, name) {
|
|
2912
|
+
const body = outcome.body;
|
|
2913
|
+
return isObject7(body) && isObject7(body[name]) ? body[name] : null;
|
|
2914
|
+
}
|
|
2915
|
+
function isObject7(value) {
|
|
2916
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2917
|
+
}
|
|
2918
|
+
function isNonEmpty3(value) {
|
|
2919
|
+
return typeof value === "string" && value.length > 0;
|
|
2920
|
+
}
|
|
2921
|
+
var UNSUPPORTED = { eligible: false, reason: "unsupportedItem" };
|
|
2922
|
+
var ORDER_REFUSED = { status: "refused", reason: "orderRefused" };
|
|
2923
|
+
var ORDER_CLOSED = { status: "refused", reason: "orderClosed" };
|
|
2924
|
+
var GRANTED = { status: "granted" };
|
|
2925
|
+
|
|
2926
|
+
// ../../stdlib/flows/iap-checkout/src/iap-verticals/pricing-plans.ts
|
|
2927
|
+
var KIND = "pricingPlan";
|
|
2928
|
+
var CHECKOUT = "/pricing-plans/v2/checkout/orders";
|
|
2929
|
+
var MEMBER_ORDERS = "/pricing-plans/v2/member/orders";
|
|
2930
|
+
var pricingPlanOrders = {
|
|
2931
|
+
kind: KIND,
|
|
2932
|
+
eligibility: (item) => singleUnitOf(KIND, item),
|
|
2933
|
+
openOrder,
|
|
2934
|
+
reopenOrder,
|
|
2935
|
+
readAccess,
|
|
2936
|
+
currentAccess
|
|
2937
|
+
};
|
|
2938
|
+
async function currentAccess(item, bridge) {
|
|
2939
|
+
let pending = false;
|
|
2940
|
+
const pages = /* @__PURE__ */ new Set();
|
|
2941
|
+
for (let offset = 0; ; offset += 50) {
|
|
2942
|
+
const answer = await bridge.call("GET", `${MEMBER_ORDERS}?orderStatuses=ACTIVE&orderStatuses=PENDING&orderStatuses=DRAFT&limit=50&offset=${offset}`);
|
|
2943
|
+
if (isRefusal(answer)) return accessRefused(answer);
|
|
2944
|
+
const orders = answer.body?.orders;
|
|
2945
|
+
if (!Array.isArray(orders)) return "unavailable";
|
|
2946
|
+
const page = JSON.stringify(orders.map((order) => [order.id, order.planId, order.status]));
|
|
2947
|
+
if (orders.length === 50 && pages.has(page)) return "unavailable";
|
|
2948
|
+
pages.add(page);
|
|
2949
|
+
const selected = orders.filter((order) => order.planId === item.reference.id);
|
|
2950
|
+
if (selected.some((order) => order.status === "ACTIVE")) return "granted";
|
|
2951
|
+
pending || (pending = selected.some((order) => order.status === "PENDING" || order.status === "DRAFT"));
|
|
2952
|
+
if (orders.length < 50) return pending ? "pending" : "none";
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
async function openOrder(item, bridge) {
|
|
2956
|
+
const planId = item.reference.id;
|
|
2957
|
+
const preview = await bridge.call("POST", `${CHECKOUT}/preview-online`, { planId });
|
|
2958
|
+
if (isRefusal(preview)) return attemptRefused(preview);
|
|
2959
|
+
if (!isOneTimeUnlimited(bodyObject(preview, "order"))) return { status: "refused", reason: "unsupportedItem" };
|
|
2960
|
+
const created = await bridge.call("POST", `${CHECKOUT}/online`, { planId });
|
|
2961
|
+
if (isRefusal(created)) return attemptRefused(created);
|
|
2962
|
+
return orderOf(bodyObject(created, "order"), planId);
|
|
2963
|
+
}
|
|
2964
|
+
async function reopenOrder(order, bridge) {
|
|
2965
|
+
const read2 = await memberOrder(order.verticalRef.orderId, bridge);
|
|
2966
|
+
if ("attempt" in read2) return read2.attempt;
|
|
2967
|
+
const attempt4 = orderOf(read2.order, order.verticalRef.planId ?? "");
|
|
2968
|
+
return attempt4.status === "open" && attempt4.cashierOrderId !== order.cashierOrderId ? ORDER_CLOSED : attempt4;
|
|
2969
|
+
}
|
|
2970
|
+
async function readAccess(verticalRef, bridge) {
|
|
2971
|
+
if (!isNonEmpty3(verticalRef.orderId)) return "unavailable";
|
|
2972
|
+
const answer = await bridge.call("GET", `${MEMBER_ORDERS}/${encodeURIComponent(verticalRef.orderId)}`);
|
|
2973
|
+
if (isRefusal(answer)) return accessRefused(answer);
|
|
2974
|
+
const order = bodyObject(answer, "order");
|
|
2975
|
+
return order ? accessOf(order) : "unavailable";
|
|
2976
|
+
}
|
|
2977
|
+
function accessOf(order) {
|
|
2978
|
+
if (order.status === "ACTIVE") return "granted";
|
|
2979
|
+
if (order.status === "PENDING") return "pending";
|
|
2980
|
+
if (order.status === "DRAFT") return "pending";
|
|
2981
|
+
return NO_ACCESS.has(String(order.status)) ? "none" : "unavailable";
|
|
2982
|
+
}
|
|
2983
|
+
var NO_ACCESS = /* @__PURE__ */ new Set(["CANCELED", "ENDED", "PAUSED"]);
|
|
2984
|
+
function orderOf(order, planId) {
|
|
2985
|
+
if (!order || !isNonEmpty3(order.id)) return ORDER_REFUSED;
|
|
2986
|
+
if (accessOf(order) === "granted") return GRANTED;
|
|
2987
|
+
if (order.status === "PENDING") return { status: "refused", reason: "paymentInProgress" };
|
|
2988
|
+
if (order.status !== "DRAFT") return ORDER_CLOSED;
|
|
2989
|
+
if (!isNonEmpty3(order.wixPayOrderId)) return ORDER_REFUSED;
|
|
2990
|
+
return { status: "open", cashierOrderId: order.wixPayOrderId, verticalRef: { orderId: order.id, planId } };
|
|
2991
|
+
}
|
|
2992
|
+
async function memberOrder(orderId, bridge) {
|
|
2993
|
+
if (!isNonEmpty3(orderId)) return { attempt: ORDER_CLOSED };
|
|
2994
|
+
const answer = await bridge.call("GET", `${MEMBER_ORDERS}/${encodeURIComponent(orderId)}`);
|
|
2995
|
+
if (isRefusal(answer)) return { attempt: answer.status === "http" && answer.httpStatus === 404 ? ORDER_CLOSED : attemptRefused(answer) };
|
|
2996
|
+
return { order: bodyObject(answer, "order") };
|
|
2997
|
+
}
|
|
2998
|
+
function isOneTimeUnlimited(order) {
|
|
2999
|
+
const pricing = order?.pricing;
|
|
3000
|
+
return pricing?.singlePaymentUnlimited === true;
|
|
3001
|
+
}
|
|
261
3002
|
|
|
262
3003
|
// ../../stdlib/wix-sdk/redirects-v1/src/index.ts
|
|
263
3004
|
function wixCallHeaders(opts) {
|
|
@@ -297,7 +3038,7 @@ var hideWhen = (n, path) => ({
|
|
|
297
3038
|
});
|
|
298
3039
|
var text = (id2, value, style = "body") => node(id2, "Text", { text: value, style });
|
|
299
3040
|
var vstack = (id2, children, spacing = 12, alignment = "leading") => node(id2, "VStack", { spacing, alignment, children });
|
|
300
|
-
var button = (id2, title,
|
|
3041
|
+
var button = (id2, title, action2, style = "primary") => node(id2, "Button", { title, style, fullWidth: true, action: action2 });
|
|
301
3042
|
var openScreen = (screenId) => ({ actionId: "himi.open", params: { screenId } });
|
|
302
3043
|
function envelope(screenId, title, root, logic) {
|
|
303
3044
|
return {
|
|
@@ -315,11 +3056,11 @@ function authScreen(opts) {
|
|
|
315
3056
|
const screenId = opts.screenId ?? "signin";
|
|
316
3057
|
const c = opts.copy ?? {};
|
|
317
3058
|
const withRegister = opts.register !== false;
|
|
318
|
-
const field = (key, label,
|
|
3059
|
+
const field = (key, label, action2, extra = {}) => node(`${s2}.${key}`, "TextField", {
|
|
319
3060
|
label,
|
|
320
3061
|
placeholder: label,
|
|
321
3062
|
valuePath: `$service:${s2}.${key}`,
|
|
322
|
-
onChange: { actionId:
|
|
3063
|
+
onChange: { actionId: action2 },
|
|
323
3064
|
...extra
|
|
324
3065
|
});
|
|
325
3066
|
const children = [
|
|
@@ -505,6 +3246,42 @@ function createCheckoutService(opts = {}) {
|
|
|
505
3246
|
const c = opts.copy ?? {};
|
|
506
3247
|
const name = opts.service ?? "plans";
|
|
507
3248
|
const withJoined = opts.showJoined !== false;
|
|
3249
|
+
const commerce = createIapCommerce({ adapters: [pricingPlanOrders], onChange: () => {
|
|
3250
|
+
void svc.pricingPublish();
|
|
3251
|
+
} });
|
|
3252
|
+
const purchaseCopy = { buy: c.joinCta ?? "Get access", processing: c.pendingNotice ?? "Processing your purchase\u2026", awaitingApproval: "Waiting for approval", unavailable: c.failedNotice ?? "Purchasing is temporarily unavailable.", signInRequired: c.signInFirst ?? "Sign in to join a plan \u2014 memberships belong to a member account.", retry: c.failedNotice ?? "Something went wrong. Try again.", priceChanged: "The price changed. Check it and try again.", support: "We couldn't finish this purchase. Contact support." };
|
|
3253
|
+
let catalogRows = [];
|
|
3254
|
+
let items = [];
|
|
3255
|
+
let started = false;
|
|
3256
|
+
const memberSession = hostSessionFromGlobal();
|
|
3257
|
+
let offerGeneration = memberSession.current().generation;
|
|
3258
|
+
let existingAccess = { entitled: false, planId: "", generation: -1 };
|
|
3259
|
+
let regularAccess = /* @__PURE__ */ new Set();
|
|
3260
|
+
let loading = 0;
|
|
3261
|
+
function project(state) {
|
|
3262
|
+
const rows = catalogRows.map((row) => {
|
|
3263
|
+
const reference = { kind: "pricingPlan", id: row.id };
|
|
3264
|
+
const view = commerce.pricing.view(reference);
|
|
3265
|
+
const price = iapPriceFields(view, { loading: "Loading price\u2026", unavailable: "Price unavailable", oneTimePurchase: "One-time purchase", purchaseUnavailable: purchaseCopy.unavailable });
|
|
3266
|
+
const purchase = commerce.purchaseFields(reference, purchaseCopy);
|
|
3267
|
+
const joined = purchase.appleRoute ? purchase.accessReady : memberSession.current().generation === offerGeneration && regularAccess.has(row.id);
|
|
3268
|
+
return {
|
|
3269
|
+
...row,
|
|
3270
|
+
joined,
|
|
3271
|
+
notJoined: !joined,
|
|
3272
|
+
joinedLabel: joined ? c.joinedLabel ?? "Current plan" : "",
|
|
3273
|
+
priceLine: view.route === "disabled" ? row.priceLine : price.priceText,
|
|
3274
|
+
periodLine: purchase.appleRoute ? price.priceNote : price.actionDisabled ? "" : row.periodLine,
|
|
3275
|
+
cta: joined ? c.joinedLabel ?? "Current plan" : purchase.appleRoute ? purchase.buyLabel : row.cta,
|
|
3276
|
+
joinDisabled: joined || (purchase.appleRoute ? purchase.buyDisabled || !row.appleSupported : price.actionDisabled || memberSession.current().generation !== offerGeneration),
|
|
3277
|
+
joinNote: purchase.appleRoute ? row.appleSupported ? purchase.buyNote : purchaseCopy.unavailable : price.actionNote
|
|
3278
|
+
};
|
|
3279
|
+
});
|
|
3280
|
+
const visibleAppleOwner = catalogRows.some((row) => row.id === existingAccess.planId && commerce.purchaseFields({ kind: "pricingPlan", id: row.id }, purchaseCopy).appleRoute);
|
|
3281
|
+
const entitled = rows.some((row) => row.joined) || existingAccess.generation === memberSession.current().generation && !visibleAppleOwner && existingAccess.entitled;
|
|
3282
|
+
const poll = commerce.pollDirective(`${name}.iapPoll`);
|
|
3283
|
+
return { ...state, rows, entitled, notEntitled: !entitled, ...poll ? { _poll: poll } : {} };
|
|
3284
|
+
}
|
|
508
3285
|
const BASE = {
|
|
509
3286
|
status: "loading",
|
|
510
3287
|
isLoading: true,
|
|
@@ -531,6 +3308,7 @@ function createCheckoutService(opts = {}) {
|
|
|
531
3308
|
joined: true,
|
|
532
3309
|
notJoined: false,
|
|
533
3310
|
joinedLabel: c.joinedLabel ?? "Current plan",
|
|
3311
|
+
joinDisabled: true,
|
|
534
3312
|
cta: c.joinedLabel ?? "Current plan"
|
|
535
3313
|
} : r
|
|
536
3314
|
);
|
|
@@ -557,13 +3335,30 @@ function createCheckoutService(opts = {}) {
|
|
|
557
3335
|
joined,
|
|
558
3336
|
notJoined: !joined,
|
|
559
3337
|
joinedLabel: joined ? c.joinedLabel ?? "Current plan" : "",
|
|
560
|
-
cta: joined ? c.joinedLabel ?? "Current plan" : c.joinCta ?? "Join"
|
|
3338
|
+
cta: joined ? c.joinedLabel ?? "Current plan" : c.joinCta ?? "Join",
|
|
3339
|
+
appleSupported: p?.pricing?.singlePaymentUnlimited === true,
|
|
3340
|
+
joinDisabled: false,
|
|
3341
|
+
joinNote: ""
|
|
561
3342
|
};
|
|
562
3343
|
};
|
|
563
3344
|
const svc = defineService(name, {
|
|
564
|
-
state: BASE,
|
|
3345
|
+
state: { ...BASE, rows: [] },
|
|
3346
|
+
notHydrated: ["rows", "entitled", "notEntitled", "notice", "needsSignIn"],
|
|
565
3347
|
actions: {
|
|
3348
|
+
pricingPublish: project,
|
|
3349
|
+
pricingRefresh: async () => {
|
|
3350
|
+
await commerce.loadPrices(items);
|
|
3351
|
+
return void 0;
|
|
3352
|
+
},
|
|
3353
|
+
iapPoll: async () => {
|
|
3354
|
+
await commerce.poll();
|
|
3355
|
+
void svc.pricingPublish();
|
|
3356
|
+
return void 0;
|
|
3357
|
+
},
|
|
566
3358
|
load: async () => {
|
|
3359
|
+
const generation = memberSession.current().generation;
|
|
3360
|
+
const seq = ++loading;
|
|
3361
|
+
offerGeneration = -1;
|
|
567
3362
|
let activePlanId = "";
|
|
568
3363
|
let entitled = false;
|
|
569
3364
|
if (withJoined) {
|
|
@@ -573,8 +3368,18 @@ function createCheckoutService(opts = {}) {
|
|
|
573
3368
|
}
|
|
574
3369
|
try {
|
|
575
3370
|
const res = await listPublicPlans();
|
|
3371
|
+
if (seq !== loading || generation !== memberSession.current().generation) return void 0;
|
|
576
3372
|
const rows = (res.plans ?? []).filter((p) => !p?.archived).map((p) => rowFrom(p, activePlanId));
|
|
577
|
-
|
|
3373
|
+
catalogRows = rows;
|
|
3374
|
+
items = rows.map((row) => ({ reference: { kind: "pricingPlan", id: row.id }, commerceDisplayPrice: row.priceLine }));
|
|
3375
|
+
await commerce.loadPrices(items);
|
|
3376
|
+
await (started ? commerce.reconcile() : commerce.start());
|
|
3377
|
+
if (seq !== loading || generation !== memberSession.current().generation) return void 0;
|
|
3378
|
+
started = true;
|
|
3379
|
+
offerGeneration = generation;
|
|
3380
|
+
existingAccess = { entitled, planId: activePlanId, generation };
|
|
3381
|
+
regularAccess = new Set(rows.filter((row) => row.joined).map((row) => row.id));
|
|
3382
|
+
return project({
|
|
578
3383
|
...BASE,
|
|
579
3384
|
status: "ready",
|
|
580
3385
|
isLoading: false,
|
|
@@ -588,8 +3393,9 @@ function createCheckoutService(opts = {}) {
|
|
|
588
3393
|
notEntitled: !entitled,
|
|
589
3394
|
needsSignIn: false,
|
|
590
3395
|
notNeedsSignIn: true
|
|
591
|
-
};
|
|
3396
|
+
});
|
|
592
3397
|
} catch {
|
|
3398
|
+
if (seq !== loading || generation !== memberSession.current().generation) return void 0;
|
|
593
3399
|
return {
|
|
594
3400
|
...BASE,
|
|
595
3401
|
status: "error",
|
|
@@ -622,12 +3428,25 @@ function createCheckoutService(opts = {}) {
|
|
|
622
3428
|
notNeedsSignIn: true
|
|
623
3429
|
};
|
|
624
3430
|
}
|
|
3431
|
+
const generation = memberSession.current().generation;
|
|
3432
|
+
const reference = { kind: "pricingPlan", id: id2 };
|
|
3433
|
+
const purchase = commerce.purchaseFields(reference, purchaseCopy);
|
|
3434
|
+
if (purchase.appleRoute) {
|
|
3435
|
+
if (!row.appleSupported) return project(s2);
|
|
3436
|
+
await commerce.buy({ reference });
|
|
3437
|
+
void svc.pricingPublish();
|
|
3438
|
+
return void 0;
|
|
3439
|
+
}
|
|
3440
|
+
if (generation !== offerGeneration || !commerce.pricing.checkoutGuard(reference).allowed) return project(s2);
|
|
625
3441
|
let purchaseFlowId = "";
|
|
626
3442
|
try {
|
|
627
3443
|
const res = await createOnlineOrder(id2);
|
|
628
3444
|
purchaseFlowId = String(res?.purchaseFlowId ?? "");
|
|
629
3445
|
const status = String(res?.order?.status ?? "").toUpperCase();
|
|
630
3446
|
if (status === GRANTS_ACCESS) {
|
|
3447
|
+
if (generation !== memberSession.current().generation) return void 0;
|
|
3448
|
+
offerGeneration = generation;
|
|
3449
|
+
regularAccess.add(id2);
|
|
631
3450
|
return {
|
|
632
3451
|
...s2,
|
|
633
3452
|
rows: markJoined(s2.rows, id2),
|
|
@@ -655,7 +3474,9 @@ function createCheckoutService(opts = {}) {
|
|
|
655
3474
|
snapshotId: purchaseFlowId,
|
|
656
3475
|
msid: "",
|
|
657
3476
|
amount: row.priceLine,
|
|
3477
|
+
mayCharge: () => commerce.pricing.continueCheckoutGuard(reference).allowed,
|
|
658
3478
|
cashierUrl: async () => {
|
|
3479
|
+
if (!commerce.pricing.continueCheckoutGuard(reference).allowed) return void 0;
|
|
659
3480
|
const r = await createRedirectSession({
|
|
660
3481
|
paidPlansCheckout: { planId: id2 },
|
|
661
3482
|
...opts.returnTo ? { callbacks: { postFlowUrl: opts.returnTo } } : {}
|
|
@@ -664,15 +3485,20 @@ function createCheckoutService(opts = {}) {
|
|
|
664
3485
|
}
|
|
665
3486
|
});
|
|
666
3487
|
if (outcome.kind === "paid") {
|
|
3488
|
+
const access = await entitlement(id2);
|
|
3489
|
+
if (generation !== memberSession.current().generation) return void 0;
|
|
3490
|
+
offerGeneration = generation;
|
|
3491
|
+
const granted = access.entitled;
|
|
3492
|
+
if (granted) regularAccess.add(id2);
|
|
667
3493
|
return {
|
|
668
3494
|
...s2,
|
|
669
3495
|
// Settled ⇒ the row is theirs. PENDING is not access yet, so its row
|
|
670
3496
|
// stays joinable and the gate stays shut until the payment clears.
|
|
671
|
-
rows:
|
|
672
|
-
notice:
|
|
3497
|
+
rows: granted ? markJoined(s2.rows, id2) : s2.rows,
|
|
3498
|
+
notice: !granted ? c.pendingNotice ?? "Payment accepted and clearing. Access opens once it settles." : c.joinedNotice ?? `You've joined ${row.name}.`,
|
|
673
3499
|
noNotice: false,
|
|
674
|
-
entitled:
|
|
675
|
-
notEntitled:
|
|
3500
|
+
entitled: granted,
|
|
3501
|
+
notEntitled: !granted,
|
|
676
3502
|
needsSignIn: false,
|
|
677
3503
|
notNeedsSignIn: true
|
|
678
3504
|
};
|
|
@@ -705,6 +3531,14 @@ function createCheckoutService(opts = {}) {
|
|
|
705
3531
|
}
|
|
706
3532
|
}
|
|
707
3533
|
});
|
|
3534
|
+
if (globalThis.himalaya?.hydrationOptOut !== true) Object.assign(svc.getState(), BASE);
|
|
3535
|
+
memberSession.subscribe(() => {
|
|
3536
|
+
offerGeneration = -1;
|
|
3537
|
+
existingAccess = { entitled: false, planId: "", generation: -1 };
|
|
3538
|
+
regularAccess = /* @__PURE__ */ new Set();
|
|
3539
|
+
void svc.pricingPublish();
|
|
3540
|
+
void svc.load();
|
|
3541
|
+
});
|
|
708
3542
|
svc.load();
|
|
709
3543
|
return svc;
|
|
710
3544
|
}
|
|
@@ -720,6 +3554,7 @@ function checkoutScreen(opts) {
|
|
|
720
3554
|
text(`${s2}.row_price`, "$item.priceLine", "title1"),
|
|
721
3555
|
text(`${s2}.row_period`, "$item.periodLine", "caption1"),
|
|
722
3556
|
text(`${s2}.row_desc`, "$item.description"),
|
|
3557
|
+
text(`${s2}.row_note`, "$item.joinNote", "caption1"),
|
|
723
3558
|
hideWhen(text(`${s2}.row_perk1`, "$item.perk1"), "$item.noPerk1"),
|
|
724
3559
|
hideWhen(text(`${s2}.row_perk2`, "$item.perk2"), "$item.noPerk2"),
|
|
725
3560
|
hideWhen(text(`${s2}.row_perk3`, "$item.perk3"), "$item.noPerk3"),
|
|
@@ -729,6 +3564,7 @@ function checkoutScreen(opts) {
|
|
|
729
3564
|
hideWhen(
|
|
730
3565
|
node(`${s2}.row_join`, "Button", {
|
|
731
3566
|
title: "$item.cta",
|
|
3567
|
+
disabledPath: "$item.joinDisabled",
|
|
732
3568
|
style: "primary",
|
|
733
3569
|
fullWidth: true,
|
|
734
3570
|
action: { actionId: `tier5.${s2}.join`, params: { id: "$item.id" } }
|
|
@@ -773,7 +3609,7 @@ function checkoutScreen(opts) {
|
|
|
773
3609
|
url: opts.bundleUrl,
|
|
774
3610
|
// wix_api reads the plans; the other two ARE the payment rail (in-app cashier,
|
|
775
3611
|
// then the hosted checkout). A paywall without them renders and cannot charge.
|
|
776
|
-
caps: ["wix_api", "wix_cashier", "open_url"]
|
|
3612
|
+
caps: ["wix_api", "wix_cashier", "open_url", "iap", "app_info"]
|
|
777
3613
|
});
|
|
778
3614
|
}
|
|
779
3615
|
export {
|