@juo/blocks 1.15.0 → 1.16.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/{block-VhmfGB3N.js → block-dVP4Rqgz.js} +9 -0
- package/dist/core.js +2294 -378
- package/dist/preact.js +2 -2
- package/dist/react.js +2 -2
- package/dist/services/customer/adapter.d.ts +8 -0
- package/dist/services/customer/adapter.d.ts.map +1 -0
- package/dist/services/customer/customerService.d.ts +3 -2
- package/dist/services/customer/customerService.d.ts.map +1 -1
- package/dist/services/customer/index.d.ts +1 -0
- package/dist/services/customer/index.d.ts.map +1 -1
- package/dist/services/customer/types.d.ts +4 -1
- package/dist/services/customer/types.d.ts.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/login/adapter.d.ts +33 -0
- package/dist/services/login/adapter.d.ts.map +1 -0
- package/dist/services/login/index.d.ts +3 -1
- package/dist/services/login/index.d.ts.map +1 -1
- package/dist/services/login/loginService.d.ts +6 -2
- package/dist/services/login/loginService.d.ts.map +1 -1
- package/dist/services/order/adapter.d.ts +9 -0
- package/dist/services/order/adapter.d.ts.map +1 -0
- package/dist/services/order/index.d.ts +5 -0
- package/dist/services/order/index.d.ts.map +1 -0
- package/dist/services/order/orderService.d.ts +7 -0
- package/dist/services/order/orderService.d.ts.map +1 -0
- package/dist/services/order/types.d.ts +21 -0
- package/dist/services/order/types.d.ts.map +1 -0
- package/dist/services/orders/adapter.d.ts +2 -0
- package/dist/services/orders/adapter.d.ts.map +1 -1
- package/dist/services/orders/index.d.ts +1 -1
- package/dist/services/orders/index.d.ts.map +1 -1
- package/dist/services/product/adapter.d.ts +27 -2
- package/dist/services/product/adapter.d.ts.map +1 -1
- package/dist/services/product/adapter.test.d.ts +2 -0
- package/dist/services/product/adapter.test.d.ts.map +1 -0
- package/dist/services/product/index.d.ts +1 -1
- package/dist/services/product/index.d.ts.map +1 -1
- package/dist/services/product/productService.d.ts.map +1 -1
- package/dist/services/product/types.d.ts +68 -0
- package/dist/services/product/types.d.ts.map +1 -1
- package/dist/services/schedules/adapter.d.ts +94 -0
- package/dist/services/schedules/adapter.d.ts.map +1 -0
- package/dist/services/schedules/adapter.test.d.ts +2 -0
- package/dist/services/schedules/adapter.test.d.ts.map +1 -0
- package/dist/services/schedules/apiSchedulesAdapter.test.d.ts +2 -0
- package/dist/services/schedules/apiSchedulesAdapter.test.d.ts.map +1 -0
- package/dist/services/schedules/index.d.ts +5 -0
- package/dist/services/schedules/index.d.ts.map +1 -0
- package/dist/services/schedules/schedulesService.d.ts +11 -0
- package/dist/services/schedules/schedulesService.d.ts.map +1 -0
- package/dist/services/schedules/schedulesService.test.d.ts +2 -0
- package/dist/services/schedules/schedulesService.test.d.ts.map +1 -0
- package/dist/services/schedules/types.d.ts +290 -0
- package/dist/services/schedules/types.d.ts.map +1 -0
- package/dist/services/settings/index.d.ts +3 -0
- package/dist/services/settings/index.d.ts.map +1 -0
- package/dist/services/settings/settingsService.d.ts +7 -0
- package/dist/services/settings/settingsService.d.ts.map +1 -0
- package/dist/services/settings/settingsService.test.d.ts +2 -0
- package/dist/services/settings/settingsService.test.d.ts.map +1 -0
- package/dist/services/settings/types.d.ts +35 -0
- package/dist/services/settings/types.d.ts.map +1 -0
- package/dist/services/shop/index.d.ts +3 -0
- package/dist/services/shop/index.d.ts.map +1 -0
- package/dist/services/shop/shopService.d.ts +12 -0
- package/dist/services/shop/shopService.d.ts.map +1 -0
- package/dist/services/shop/shopService.test.d.ts +2 -0
- package/dist/services/shop/shopService.test.d.ts.map +1 -0
- package/dist/services/shop/types.d.ts +7 -0
- package/dist/services/shop/types.d.ts.map +1 -0
- package/dist/services/subscription/adapter.d.ts +77 -2
- package/dist/services/subscription/adapter.d.ts.map +1 -1
- package/dist/services/subscription/adapter.test.d.ts +2 -0
- package/dist/services/subscription/adapter.test.d.ts.map +1 -0
- package/dist/services/subscription/index.d.ts +1 -1
- package/dist/services/subscription/index.d.ts.map +1 -1
- package/dist/services/subscription/subscriptionService.d.ts +1 -6
- package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
- package/dist/services/subscription/types.d.ts +220 -9
- package/dist/services/subscription/types.d.ts.map +1 -1
- package/dist/services/subscription/types.test.d.ts +2 -0
- package/dist/services/subscription/types.test.d.ts.map +1 -0
- package/dist/shared/context.d.ts.map +1 -1
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/vue/context.d.ts +1 -0
- package/dist/vue/context.d.ts.map +1 -1
- package/dist/vue.js +12 -2
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -159,13 +159,43 @@ async function loadBlockRegistry(options = {}) {
|
|
|
159
159
|
function createCustomerService(options) {
|
|
160
160
|
const current = d(null);
|
|
161
161
|
const accountOrdersUrl = d(options.getAccountOrdersUrl());
|
|
162
|
-
|
|
162
|
+
return {
|
|
163
163
|
current,
|
|
164
|
-
accountOrdersUrl
|
|
164
|
+
accountOrdersUrl,
|
|
165
|
+
async getCurrent() {
|
|
166
|
+
const result = await options.adapter.getCurrent();
|
|
167
|
+
if (result._tag === "Success") current.value = result.data;
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
165
170
|
};
|
|
166
|
-
return service;
|
|
167
171
|
}
|
|
168
172
|
const CustomerServiceContext = createContext("services/customer");
|
|
173
|
+
function createMockCustomerAdapter() {
|
|
174
|
+
const customer = { id: "customer-1", name: "Customer 1" };
|
|
175
|
+
return {
|
|
176
|
+
async getCurrent() {
|
|
177
|
+
return { _tag: "Success", data: customer };
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function createApiCustomerAdapter(fetcher) {
|
|
182
|
+
return {
|
|
183
|
+
async getCurrent() {
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetcher("/customer/info");
|
|
186
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
187
|
+
const data = await res.json();
|
|
188
|
+
const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
|
|
189
|
+
return {
|
|
190
|
+
_tag: "Success",
|
|
191
|
+
data: { id: data.id, name, email: data.email }
|
|
192
|
+
};
|
|
193
|
+
} catch (e2) {
|
|
194
|
+
return { _tag: "Failure", error: e2 };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
169
199
|
const PARAMS = {
|
|
170
200
|
DELETGATED_TOKEN: "delegated-token",
|
|
171
201
|
REDIRECT_TO: "redirect-to",
|
|
@@ -182,6 +212,123 @@ function uuid() {
|
|
|
182
212
|
(c2) => (c2 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c2 / 4).toString(16)
|
|
183
213
|
);
|
|
184
214
|
}
|
|
215
|
+
function createMockLoginAdapter(opts = {}) {
|
|
216
|
+
const appUrl = opts.appUrl ?? "https://admin.juo.io";
|
|
217
|
+
return {
|
|
218
|
+
async passwordlessStart() {
|
|
219
|
+
return { _tag: "Success", data: void 0 };
|
|
220
|
+
},
|
|
221
|
+
async authenticate() {
|
|
222
|
+
return {
|
|
223
|
+
_tag: "Success",
|
|
224
|
+
data: {
|
|
225
|
+
access_token: "mock-access-token",
|
|
226
|
+
refresh_token: "mock-refresh-token",
|
|
227
|
+
expires_in: 3600
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
getAuthorizeUrl({ connection, state, shop }) {
|
|
232
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
233
|
+
url.searchParams.set("connection", connection);
|
|
234
|
+
url.searchParams.set("state", state);
|
|
235
|
+
url.searchParams.set("shop", shop);
|
|
236
|
+
return url.toString();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function createApiLoginAdapter(fetcher, opts) {
|
|
241
|
+
const { shopDomain, appUrl } = opts;
|
|
242
|
+
async function callAuthEndpoint(path, body, lang) {
|
|
243
|
+
const url = lang ? `${path}?lang=${lang}` : path;
|
|
244
|
+
const res = await fetcher(url, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
Accept: "application/json",
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify(body)
|
|
252
|
+
});
|
|
253
|
+
if (res.status === 403) {
|
|
254
|
+
const data = await res.json();
|
|
255
|
+
if ("error_description" in data) {
|
|
256
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
257
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
258
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
259
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
260
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
261
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
262
|
+
}
|
|
263
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
264
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
265
|
+
connection: data.connection
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
271
|
+
}
|
|
272
|
+
return { _tag: "Success", data: void 0 };
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
async passwordlessStart({ email, lang }) {
|
|
276
|
+
try {
|
|
277
|
+
await callAuthEndpoint(
|
|
278
|
+
"/auth/customer/passwordless/start",
|
|
279
|
+
{ email },
|
|
280
|
+
lang
|
|
281
|
+
);
|
|
282
|
+
return { _tag: "Success", data: void 0 };
|
|
283
|
+
} catch (e2) {
|
|
284
|
+
return { _tag: "Failure", error: e2 };
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async authenticate(params) {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetcher("/auth/customer/token", {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
Accept: "application/json",
|
|
293
|
+
"Content-Type": "application/json",
|
|
294
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify(params)
|
|
297
|
+
});
|
|
298
|
+
if (res.status === 403) {
|
|
299
|
+
const data = await res.json();
|
|
300
|
+
if ("error_description" in data) {
|
|
301
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
302
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
303
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
304
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
305
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
306
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
307
|
+
}
|
|
308
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
309
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
310
|
+
connection: data.connection
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!res.ok) {
|
|
315
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
316
|
+
}
|
|
317
|
+
return { _tag: "Success", data: await res.json() };
|
|
318
|
+
} catch (e2) {
|
|
319
|
+
return { _tag: "Failure", error: e2 };
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
getAuthorizeUrl({ connection, state, shop }) {
|
|
323
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
324
|
+
url.searchParams.set("connection", connection);
|
|
325
|
+
url.searchParams.set("state", state);
|
|
326
|
+
url.searchParams.set("shop", shop);
|
|
327
|
+
return url.toString();
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const __vite_import_meta_env__ = {};
|
|
185
332
|
var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
|
|
186
333
|
AuthenticationErrorReason2["Expired"] = "expired";
|
|
187
334
|
AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
|
|
@@ -200,6 +347,9 @@ class AuthenticationError extends Error {
|
|
|
200
347
|
this.data = data ?? {};
|
|
201
348
|
}
|
|
202
349
|
}
|
|
350
|
+
const STORAGE_DELEGATED_TOKEN = "delegated-token";
|
|
351
|
+
const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
|
|
352
|
+
const STORAGE_STATE_KEY = "state";
|
|
203
353
|
function storage(key, value) {
|
|
204
354
|
const storageKey = `auth:${key}`;
|
|
205
355
|
try {
|
|
@@ -215,83 +365,33 @@ function storage(key, value) {
|
|
|
215
365
|
}
|
|
216
366
|
return null;
|
|
217
367
|
}
|
|
218
|
-
const STORAGE_DELEGATED_TOKEN = "delegated-token";
|
|
219
|
-
const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
|
|
220
|
-
const STORAGE_STATE_KEY = "state";
|
|
221
|
-
function createAuthFunctions(basePath, shop) {
|
|
222
|
-
async function start(email, lang) {
|
|
223
|
-
const response = await fetch(
|
|
224
|
-
`${basePath}/auth/customer/passwordless/start?lang=${lang}`,
|
|
225
|
-
{
|
|
226
|
-
method: "POST",
|
|
227
|
-
headers: {
|
|
228
|
-
Accept: "application/json",
|
|
229
|
-
"Content-Type": "application/json",
|
|
230
|
-
"X-Shopify-Shop-Domain": shop
|
|
231
|
-
},
|
|
232
|
-
body: JSON.stringify({ email })
|
|
233
|
-
}
|
|
234
|
-
);
|
|
235
|
-
if (response.status !== 200) {
|
|
236
|
-
throw new Error(`Unexpected response: ${response.statusText}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
async function authenticate(data) {
|
|
240
|
-
const response = await fetch(`${basePath}/auth/customer/token`, {
|
|
241
|
-
method: "POST",
|
|
242
|
-
headers: {
|
|
243
|
-
Accept: "application/json",
|
|
244
|
-
"Content-Type": "application/json",
|
|
245
|
-
"X-Shopify-Shop-Domain": shop
|
|
246
|
-
},
|
|
247
|
-
body: JSON.stringify(data)
|
|
248
|
-
});
|
|
249
|
-
if (response.status === 403) {
|
|
250
|
-
const data2 = await response.json();
|
|
251
|
-
if ("error_description" in data2) {
|
|
252
|
-
if (data2.error_description === "Wrong email or verification code.") {
|
|
253
|
-
throw new AuthenticationError(
|
|
254
|
-
"wrong-code"
|
|
255
|
-
/* IncorrectCode */
|
|
256
|
-
);
|
|
257
|
-
} else if (data2.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
258
|
-
throw new AuthenticationError(
|
|
259
|
-
"too-many-tries"
|
|
260
|
-
/* TooManyTries */
|
|
261
|
-
);
|
|
262
|
-
} else if (data2.error_description === "The verification code has expired. Please try to login again.") {
|
|
263
|
-
throw new AuthenticationError(
|
|
264
|
-
"expired"
|
|
265
|
-
/* Expired */
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
} else if ("code" in data2) {
|
|
269
|
-
if (data2.code === "AUTH_ERROR") {
|
|
270
|
-
throw new AuthenticationError("no-email", {
|
|
271
|
-
connection: data2.connection
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (response.status !== 200) {
|
|
277
|
-
throw new Error(`Unexpected response: ${response.statusText}`);
|
|
278
|
-
}
|
|
279
|
-
return await response.json();
|
|
280
|
-
}
|
|
281
|
-
return { start, authenticate };
|
|
282
|
-
}
|
|
283
368
|
function nonce(len = 10) {
|
|
284
369
|
return uuid().replace(/-/g, "").slice(0, len);
|
|
285
370
|
}
|
|
286
|
-
function
|
|
287
|
-
const
|
|
371
|
+
function loginService(deps) {
|
|
372
|
+
const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
|
|
373
|
+
const appUrl = (typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io";
|
|
374
|
+
const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
|
|
375
|
+
const adapter = createApiLoginAdapter(fetcher, {
|
|
376
|
+
shopDomain: deps.shop.domain,
|
|
377
|
+
appUrl
|
|
378
|
+
});
|
|
379
|
+
return createLoginService({
|
|
380
|
+
adapter,
|
|
381
|
+
router: deps.router,
|
|
382
|
+
formatError: deps.formatError,
|
|
383
|
+
shop: {
|
|
384
|
+
domain: deps.shop.domain,
|
|
385
|
+
locale: w(() => deps.getLocale())
|
|
386
|
+
},
|
|
387
|
+
onLoginSuccess: deps.onLoginSuccess
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function createLoginService(init) {
|
|
391
|
+
const { adapter, formatError, router, shop, onLoginSuccess } = init;
|
|
288
392
|
const accessToken = d(null);
|
|
289
|
-
const delegatedToken = d(
|
|
290
|
-
|
|
291
|
-
);
|
|
292
|
-
const refreshToken = d(
|
|
293
|
-
storage(STORAGE_REFRESH_TOKEN_KEY)
|
|
294
|
-
);
|
|
393
|
+
const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
|
|
394
|
+
const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
|
|
295
395
|
const expires = d(null);
|
|
296
396
|
const state = d(storage(STORAGE_STATE_KEY));
|
|
297
397
|
E(() => {
|
|
@@ -303,67 +403,28 @@ function createAuthService(apiUrl, shopDomain) {
|
|
|
303
403
|
E(() => {
|
|
304
404
|
storage(STORAGE_STATE_KEY, state.value);
|
|
305
405
|
});
|
|
306
|
-
function
|
|
406
|
+
function storeTokens(data) {
|
|
307
407
|
const { access_token, refresh_token, expires_in } = data;
|
|
308
408
|
accessToken.value = access_token;
|
|
309
409
|
refreshToken.value = refresh_token;
|
|
310
410
|
expires.value = new Date(Date.now() + expires_in);
|
|
311
411
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
getDelegatedToken() {
|
|
321
|
-
return delegatedToken.value;
|
|
322
|
-
},
|
|
323
|
-
setDelegatedToken(token) {
|
|
324
|
-
delegatedToken.value = token;
|
|
325
|
-
},
|
|
326
|
-
async getToken() {
|
|
327
|
-
if (refreshToken.value == null) {
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
|
|
331
|
-
accessToken.value = new Promise(
|
|
332
|
-
(resolve, reject) => authAuthenticate({ refreshToken: refreshToken.value }).then((data) => data).then((data) => {
|
|
333
|
-
store(data);
|
|
334
|
-
resolve(data.access_token);
|
|
412
|
+
async function getToken() {
|
|
413
|
+
if (refreshToken.value == null) return null;
|
|
414
|
+
if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
|
|
415
|
+
accessToken.value = new Promise(
|
|
416
|
+
(resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
|
|
417
|
+
if (result._tag === "Failure") {
|
|
418
|
+
reject(result.error);
|
|
335
419
|
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
refreshToken.value = null;
|
|
345
|
-
expires.value = null;
|
|
346
|
-
},
|
|
347
|
-
isAuthenticated: w(
|
|
348
|
-
() => delegatedToken.value != null || refreshToken.value != null
|
|
349
|
-
)
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function loginService(deps) {
|
|
353
|
-
return createLoginService({
|
|
354
|
-
apiUrl: `${deps.shop.rootUrl.replace(/\/$/, "")}/api`,
|
|
355
|
-
router: deps.router,
|
|
356
|
-
formatError: deps.formatError,
|
|
357
|
-
shop: {
|
|
358
|
-
domain: deps.shop.domain,
|
|
359
|
-
locale: w(() => deps.getLocale())
|
|
360
|
-
},
|
|
361
|
-
onLoginSuccess: deps.onLoginSuccess
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
function createLoginService(init) {
|
|
365
|
-
const { formatError, router, shop, onLoginSuccess } = init;
|
|
366
|
-
const auth = createAuthService(init.apiUrl.replace(/\/$/, ""), shop.domain);
|
|
420
|
+
}
|
|
421
|
+
storeTokens(result.data);
|
|
422
|
+
resolve(result.data.access_token);
|
|
423
|
+
}).catch(reject)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return Promise.resolve(accessToken.value);
|
|
427
|
+
}
|
|
367
428
|
const userEmail = d(null);
|
|
368
429
|
async function onSuccess(query) {
|
|
369
430
|
await (onLoginSuccess == null ? void 0 : onLoginSuccess());
|
|
@@ -376,13 +437,9 @@ function createLoginService(init) {
|
|
|
376
437
|
queryParams[key] = value;
|
|
377
438
|
}
|
|
378
439
|
});
|
|
379
|
-
router.push({
|
|
380
|
-
path: decodeURIComponent(redirect),
|
|
381
|
-
query: queryParams
|
|
382
|
-
});
|
|
440
|
+
router.push({ path: decodeURIComponent(redirect), query: queryParams });
|
|
383
441
|
} else {
|
|
384
|
-
|
|
385
|
-
router.replace(decodeURIComponent(redirect));
|
|
442
|
+
router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
|
|
386
443
|
}
|
|
387
444
|
} else {
|
|
388
445
|
router.replace("/");
|
|
@@ -394,16 +451,17 @@ function createLoginService(init) {
|
|
|
394
451
|
async start(email) {
|
|
395
452
|
try {
|
|
396
453
|
userEmail.value = email;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
454
|
+
const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
|
|
455
|
+
const result = await adapter.passwordlessStart({
|
|
456
|
+
email: userEmail.value,
|
|
457
|
+
lang
|
|
458
|
+
});
|
|
459
|
+
if (result._tag === "Failure") {
|
|
460
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
461
|
+
}
|
|
401
462
|
return { _tag: "Success", data: void 0 };
|
|
402
463
|
} catch (error) {
|
|
403
|
-
return {
|
|
404
|
-
_tag: "Failure",
|
|
405
|
-
error: formatError(error)
|
|
406
|
-
};
|
|
464
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
407
465
|
}
|
|
408
466
|
},
|
|
409
467
|
async verify(email, code) {
|
|
@@ -414,63 +472,57 @@ function createLoginService(init) {
|
|
|
414
472
|
};
|
|
415
473
|
}
|
|
416
474
|
try {
|
|
417
|
-
const result = await
|
|
475
|
+
const result = await adapter.authenticate({
|
|
418
476
|
email: userEmail.value,
|
|
419
477
|
code
|
|
420
478
|
});
|
|
479
|
+
if (result._tag === "Failure") {
|
|
480
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
481
|
+
}
|
|
482
|
+
storeTokens(result.data);
|
|
421
483
|
await onSuccess();
|
|
422
|
-
return { _tag: "Success", data:
|
|
484
|
+
return { _tag: "Success", data: void 0 };
|
|
423
485
|
} catch (error) {
|
|
424
|
-
return {
|
|
425
|
-
_tag: "Failure",
|
|
426
|
-
error: formatError(error)
|
|
427
|
-
};
|
|
486
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
428
487
|
}
|
|
429
488
|
}
|
|
430
489
|
}
|
|
431
490
|
},
|
|
432
491
|
social: {
|
|
433
492
|
async start(provider) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
url.searchParams.append("shop", shop2);
|
|
443
|
-
return url.toString();
|
|
444
|
-
}
|
|
445
|
-
window.location.href = socialUrl(
|
|
446
|
-
provider === "Facebook" ? "facebook" : "google-oauth2",
|
|
447
|
-
shop.domain
|
|
448
|
-
);
|
|
449
|
-
return {
|
|
450
|
-
_tag: "Success",
|
|
451
|
-
data: void 0
|
|
452
|
-
};
|
|
493
|
+
state.value = nonce();
|
|
494
|
+
const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
|
|
495
|
+
window.location.href = adapter.getAuthorizeUrl({
|
|
496
|
+
connection,
|
|
497
|
+
state: state.value ?? "",
|
|
498
|
+
shop: shop.domain
|
|
499
|
+
});
|
|
500
|
+
return { _tag: "Success", data: void 0 };
|
|
453
501
|
},
|
|
454
502
|
async verify(code) {
|
|
455
503
|
try {
|
|
456
|
-
const result = await
|
|
504
|
+
const result = await adapter.authenticate({ code });
|
|
505
|
+
if (result._tag === "Failure") {
|
|
506
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
507
|
+
}
|
|
508
|
+
storeTokens(result.data);
|
|
457
509
|
await onSuccess();
|
|
458
|
-
return { _tag: "Success", data:
|
|
510
|
+
return { _tag: "Success", data: void 0 };
|
|
459
511
|
} catch (error) {
|
|
460
|
-
return {
|
|
461
|
-
_tag: "Failure",
|
|
462
|
-
error: formatError(error)
|
|
463
|
-
};
|
|
512
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
464
513
|
}
|
|
465
514
|
}
|
|
466
515
|
},
|
|
467
516
|
async logout() {
|
|
468
|
-
|
|
517
|
+
accessToken.value = null;
|
|
518
|
+
delegatedToken.value = null;
|
|
519
|
+
refreshToken.value = null;
|
|
520
|
+
expires.value = null;
|
|
469
521
|
return { _tag: "Success", data: void 0 };
|
|
470
522
|
},
|
|
471
523
|
async handleQueryParams(query) {
|
|
472
524
|
if (query.has(PARAMS.DELETGATED_TOKEN)) {
|
|
473
|
-
|
|
525
|
+
delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
|
|
474
526
|
await onSuccess(query);
|
|
475
527
|
return { _tag: "Success", data: void 0 };
|
|
476
528
|
}
|
|
@@ -482,16 +534,13 @@ function createLoginService(init) {
|
|
|
482
534
|
}
|
|
483
535
|
const code = query.get(PARAMS.CODE);
|
|
484
536
|
if (code != null) {
|
|
485
|
-
if (query.get(PARAMS.STATE) !==
|
|
486
|
-
return {
|
|
487
|
-
_tag: "Failure",
|
|
488
|
-
error: formatError("Invalid state")
|
|
489
|
-
};
|
|
537
|
+
if (query.get(PARAMS.STATE) !== state.value) {
|
|
538
|
+
return { _tag: "Failure", error: formatError("Invalid state") };
|
|
490
539
|
} else {
|
|
491
540
|
return service.social.verify(code);
|
|
492
541
|
}
|
|
493
542
|
}
|
|
494
|
-
if (query.get(PARAMS.REFRESH) === "1" && await
|
|
543
|
+
if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
|
|
495
544
|
await onSuccess();
|
|
496
545
|
return { _tag: "Success", data: void 0 };
|
|
497
546
|
}
|
|
@@ -508,8 +557,20 @@ function createLoginService(init) {
|
|
|
508
557
|
}
|
|
509
558
|
return { _tag: "Success", data: void 0 };
|
|
510
559
|
},
|
|
511
|
-
isAuthenticated:
|
|
512
|
-
|
|
560
|
+
isAuthenticated: w(
|
|
561
|
+
() => delegatedToken.value != null || refreshToken.value != null
|
|
562
|
+
),
|
|
563
|
+
getToken,
|
|
564
|
+
async getAuthHeaders() {
|
|
565
|
+
if (delegatedToken.value) {
|
|
566
|
+
return { "X-Delegated-Token": delegatedToken.value };
|
|
567
|
+
}
|
|
568
|
+
const token = await getToken();
|
|
569
|
+
if (token) {
|
|
570
|
+
return { Authorization: `Bearer ${token}` };
|
|
571
|
+
}
|
|
572
|
+
return {};
|
|
573
|
+
}
|
|
513
574
|
};
|
|
514
575
|
return service;
|
|
515
576
|
}
|
|
@@ -575,6 +636,55 @@ function createMockOrdersAdapter() {
|
|
|
575
636
|
}
|
|
576
637
|
};
|
|
577
638
|
}
|
|
639
|
+
function mapOrderLine(item) {
|
|
640
|
+
var _a;
|
|
641
|
+
return {
|
|
642
|
+
id: item.id,
|
|
643
|
+
title: item.title,
|
|
644
|
+
quantity: item.quantity,
|
|
645
|
+
variantImage: ((_a = item.variantImage) == null ? void 0 : _a.url) ? { url: item.variantImage.url, altText: item.variantImage.altText ?? void 0 } : null,
|
|
646
|
+
customAttributes: item.customAttributes
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function mapOrder(order) {
|
|
650
|
+
return {
|
|
651
|
+
id: order.id,
|
|
652
|
+
createdAt: order.createdAt,
|
|
653
|
+
placedAt: order.placedAt,
|
|
654
|
+
currencyCode: order.currencyCode,
|
|
655
|
+
totalPrice: order.totalPrice,
|
|
656
|
+
financialStatus: order.financialStatus,
|
|
657
|
+
fulfillmentStatus: order.fulfillmentStatus,
|
|
658
|
+
lines: order.items.map(mapOrderLine),
|
|
659
|
+
customAttributes: order.customAttributes
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function createApiOrdersAdapter(fetcher) {
|
|
663
|
+
return {
|
|
664
|
+
async getOrders(params) {
|
|
665
|
+
const query = new URLSearchParams();
|
|
666
|
+
if ((params == null ? void 0 : params.limit) != null) query.set("limit", String(params.limit));
|
|
667
|
+
if ((params == null ? void 0 : params.after) != null) query.set("after", params.after);
|
|
668
|
+
if ((params == null ? void 0 : params.before) != null) query.set("before", params.before);
|
|
669
|
+
if ((params == null ? void 0 : params.query) != null) query.set("query", params.query);
|
|
670
|
+
if ((params == null ? void 0 : params.includeOneTime) != null) query.set("includeOneTime", String(params.includeOneTime));
|
|
671
|
+
if ((params == null ? void 0 : params.sort) != null) query.set("sort", params.sort);
|
|
672
|
+
const qs = query.toString();
|
|
673
|
+
const res = await fetcher(`/customer/v1/orders${qs ? `?${qs}` : ""}`);
|
|
674
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
675
|
+
const body = await res.json();
|
|
676
|
+
return {
|
|
677
|
+
items: body.data.map(mapOrder),
|
|
678
|
+
pageInfo: {
|
|
679
|
+
hasNextPage: body.hasNextPage,
|
|
680
|
+
hasPrevPage: body.hasPrevPage,
|
|
681
|
+
startCursor: body.startCursor,
|
|
682
|
+
endCursor: body.endCursor
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
}
|
|
578
688
|
function createOrdersService(adapter) {
|
|
579
689
|
return {
|
|
580
690
|
async getOrders(params) {
|
|
@@ -594,7 +704,7 @@ function createMockProductAdapter() {
|
|
|
594
704
|
id: "sample-1",
|
|
595
705
|
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
596
706
|
featuredImage: {
|
|
597
|
-
url: "https://
|
|
707
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
598
708
|
},
|
|
599
709
|
hasOnlyDefaultVariant: true,
|
|
600
710
|
variants: [
|
|
@@ -602,7 +712,7 @@ function createMockProductAdapter() {
|
|
|
602
712
|
id: "sample-1-1",
|
|
603
713
|
title: "150 ml bottle",
|
|
604
714
|
image: {
|
|
605
|
-
url: "https://
|
|
715
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
606
716
|
},
|
|
607
717
|
price: {
|
|
608
718
|
amount: "12.49",
|
|
@@ -628,7 +738,7 @@ function createMockProductAdapter() {
|
|
|
628
738
|
id: "sample-2",
|
|
629
739
|
title: "More COLLAGEN",
|
|
630
740
|
featuredImage: {
|
|
631
|
-
url: "https://
|
|
741
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
632
742
|
},
|
|
633
743
|
hasOnlyDefaultVariant: true,
|
|
634
744
|
variants: [
|
|
@@ -636,7 +746,7 @@ function createMockProductAdapter() {
|
|
|
636
746
|
id: "sample-2-1",
|
|
637
747
|
title: "blackcurrant",
|
|
638
748
|
image: {
|
|
639
|
-
url: "https://
|
|
749
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
640
750
|
},
|
|
641
751
|
price: {
|
|
642
752
|
amount: "12.49",
|
|
@@ -657,7 +767,7 @@ function createMockProductAdapter() {
|
|
|
657
767
|
id: "sample-3",
|
|
658
768
|
title: "Gentle deodorant for underarms and bust",
|
|
659
769
|
featuredImage: {
|
|
660
|
-
url: "https://
|
|
770
|
+
url: "https://placehold.co/400x400?text=Product+3"
|
|
661
771
|
},
|
|
662
772
|
hasOnlyDefaultVariant: false,
|
|
663
773
|
variants: [
|
|
@@ -665,7 +775,7 @@ function createMockProductAdapter() {
|
|
|
665
775
|
id: "sample-3-1",
|
|
666
776
|
title: "Green tea and jasmine",
|
|
667
777
|
image: {
|
|
668
|
-
url: "https://
|
|
778
|
+
url: "https://placehold.co/400x400?text=Product+3a"
|
|
669
779
|
},
|
|
670
780
|
price: {
|
|
671
781
|
amount: "6.7",
|
|
@@ -683,7 +793,7 @@ function createMockProductAdapter() {
|
|
|
683
793
|
id: "sample-3-2",
|
|
684
794
|
title: "Lavender and mint",
|
|
685
795
|
image: {
|
|
686
|
-
url: "https://
|
|
796
|
+
url: "https://placehold.co/400x400?text=Product+3b"
|
|
687
797
|
},
|
|
688
798
|
price: {
|
|
689
799
|
amount: "7.5",
|
|
@@ -715,6 +825,48 @@ function createMockProductAdapter() {
|
|
|
715
825
|
}
|
|
716
826
|
};
|
|
717
827
|
}
|
|
828
|
+
function toAvailableProduct(product) {
|
|
829
|
+
return {
|
|
830
|
+
id: product.id,
|
|
831
|
+
title: product.title,
|
|
832
|
+
description: `Sample description for ${product.title}.`,
|
|
833
|
+
featuredImage: product.featuredImage ?? void 0,
|
|
834
|
+
hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
|
|
835
|
+
variants: product.variants.map((variant) => {
|
|
836
|
+
var _a, _b, _c;
|
|
837
|
+
const base = Number.parseFloat(variant.price.amount);
|
|
838
|
+
const sub = (_b = (_a = variant.purchaseOptions) == null ? void 0 : _a[0]) == null ? void 0 : _b.price.amount;
|
|
839
|
+
const current = sub == null ? base : Number.parseFloat(sub);
|
|
840
|
+
return {
|
|
841
|
+
id: variant.id,
|
|
842
|
+
availableForSale: true,
|
|
843
|
+
title: variant.title,
|
|
844
|
+
currentPrice: { amount: current, currencyCode: variant.price.currencyCode },
|
|
845
|
+
basePrice: { amount: base, currencyCode: variant.price.currencyCode },
|
|
846
|
+
image: variant.image ?? void 0,
|
|
847
|
+
recurring: (((_c = variant.purchaseOptions) == null ? void 0 : _c.length) ?? 0) > 0
|
|
848
|
+
};
|
|
849
|
+
})
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
const sampleBox = {
|
|
853
|
+
id: "sample-box-1",
|
|
854
|
+
title: "Build Your Own Box",
|
|
855
|
+
minProductsCount: 3,
|
|
856
|
+
parentProduct: "sample-1",
|
|
857
|
+
tiers: [
|
|
858
|
+
{ value: 5, threshold: 3 },
|
|
859
|
+
{ value: 10, threshold: 5 }
|
|
860
|
+
],
|
|
861
|
+
variants: products.map((product) => ({
|
|
862
|
+
id: product.id,
|
|
863
|
+
variants: product.variants.map((variant) => ({ id: variant.id }))
|
|
864
|
+
})),
|
|
865
|
+
freeVariants: [],
|
|
866
|
+
thresholdType: "LINES_COUNT",
|
|
867
|
+
type: "PERCENTAGE",
|
|
868
|
+
restrictQuantityToTiers: false
|
|
869
|
+
};
|
|
718
870
|
return {
|
|
719
871
|
async getById(id) {
|
|
720
872
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -728,6 +880,27 @@ function createMockProductAdapter() {
|
|
|
728
880
|
);
|
|
729
881
|
return product == null ? null : processProduct(product);
|
|
730
882
|
},
|
|
883
|
+
async getProductsByIds({ ids }) {
|
|
884
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
885
|
+
return products.filter((product) => ids.includes(product.id)).map(({ variants: _variants, ...product }) => product);
|
|
886
|
+
},
|
|
887
|
+
async getBoxVariants({ ids }) {
|
|
888
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
889
|
+
return products.filter((product) => ids.includes(product.id)).map(toAvailableProduct);
|
|
890
|
+
},
|
|
891
|
+
async getAvailableProducts({ q }) {
|
|
892
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
893
|
+
const queryLower = q == null ? void 0 : q.toLowerCase();
|
|
894
|
+
return products.filter(
|
|
895
|
+
(product) => !queryLower || product.title.toLowerCase().includes(queryLower) || product.variants.some(
|
|
896
|
+
(variant) => variant.title.toLowerCase().includes(queryLower)
|
|
897
|
+
)
|
|
898
|
+
).map(toAvailableProduct);
|
|
899
|
+
},
|
|
900
|
+
async getBox({ id }) {
|
|
901
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
902
|
+
return id === sampleBox.id ? sampleBox : null;
|
|
903
|
+
},
|
|
731
904
|
async search(opts) {
|
|
732
905
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
733
906
|
let filteredProducts = [...products];
|
|
@@ -768,6 +941,185 @@ function createMockProductAdapter() {
|
|
|
768
941
|
}
|
|
769
942
|
};
|
|
770
943
|
}
|
|
944
|
+
function mapVariant(variant, currencyCode) {
|
|
945
|
+
var _a;
|
|
946
|
+
return {
|
|
947
|
+
id: variant.id,
|
|
948
|
+
title: variant.title,
|
|
949
|
+
image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
|
|
950
|
+
price: { amount: variant.price.toFixed(2), currencyCode },
|
|
951
|
+
...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
|
|
952
|
+
purchaseOptions: variant.purchaseOptions.map((opt) => ({
|
|
953
|
+
name: opt.name,
|
|
954
|
+
price: { amount: opt.price.toFixed(2), currencyCode },
|
|
955
|
+
interval: opt.interval
|
|
956
|
+
}))
|
|
957
|
+
} : {}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function mapProduct(product, currencyCode) {
|
|
961
|
+
return {
|
|
962
|
+
id: product.id,
|
|
963
|
+
title: product.title,
|
|
964
|
+
featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
|
|
965
|
+
hasOnlyDefaultVariant: product.variants.length <= 1,
|
|
966
|
+
variants: {
|
|
967
|
+
items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
|
|
968
|
+
pageInfo: {
|
|
969
|
+
hasNextPage: false,
|
|
970
|
+
hasPrevPage: false,
|
|
971
|
+
startCursor: null,
|
|
972
|
+
endCursor: null
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
collections: product.collections
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
|
|
979
|
+
const { currencyCode } = opts;
|
|
980
|
+
async function fetchProducts(params) {
|
|
981
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
982
|
+
const res = await fetcher(`/customer/v1/products${query}`);
|
|
983
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
984
|
+
return res.json();
|
|
985
|
+
}
|
|
986
|
+
return {
|
|
987
|
+
async getById(id) {
|
|
988
|
+
try {
|
|
989
|
+
const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
|
|
990
|
+
const body = await fetchProducts(params);
|
|
991
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
992
|
+
} catch {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
async getByVariantId(variantId) {
|
|
997
|
+
try {
|
|
998
|
+
const params = new URLSearchParams({
|
|
999
|
+
query: `variants.id:"${variantId}"`,
|
|
1000
|
+
limit: "1"
|
|
1001
|
+
});
|
|
1002
|
+
const body = await fetchProducts(params);
|
|
1003
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1004
|
+
} catch {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
async search(opts2) {
|
|
1009
|
+
try {
|
|
1010
|
+
const params = new URLSearchParams();
|
|
1011
|
+
if (opts2 == null ? void 0 : opts2.query) {
|
|
1012
|
+
params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
|
|
1013
|
+
}
|
|
1014
|
+
if (opts2 == null ? void 0 : opts2.pagination) {
|
|
1015
|
+
if ("after" in opts2.pagination)
|
|
1016
|
+
params.set("after", opts2.pagination.after);
|
|
1017
|
+
else if ("before" in opts2.pagination)
|
|
1018
|
+
params.set("before", opts2.pagination.before);
|
|
1019
|
+
}
|
|
1020
|
+
const body = await fetchProducts(params);
|
|
1021
|
+
return {
|
|
1022
|
+
items: body.data.map((p2) => mapProduct(p2, currencyCode)),
|
|
1023
|
+
pageInfo: {
|
|
1024
|
+
hasNextPage: body.hasNextPage,
|
|
1025
|
+
hasPrevPage: body.hasPrevPage,
|
|
1026
|
+
startCursor: body.startCursor,
|
|
1027
|
+
endCursor: body.endCursor
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
} catch {
|
|
1031
|
+
return {
|
|
1032
|
+
items: [],
|
|
1033
|
+
pageInfo: {
|
|
1034
|
+
hasNextPage: false,
|
|
1035
|
+
hasPrevPage: false,
|
|
1036
|
+
startCursor: null,
|
|
1037
|
+
endCursor: null
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
async getProductsByIds({ ids }) {
|
|
1043
|
+
const params = new URLSearchParams();
|
|
1044
|
+
for (const id of ids) params.append("ids", id);
|
|
1045
|
+
const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
|
|
1046
|
+
if (!res.ok) return [];
|
|
1047
|
+
const data = await res.json();
|
|
1048
|
+
return (data ?? []).map((p2) => ({
|
|
1049
|
+
id: p2.id,
|
|
1050
|
+
title: p2.title ?? "",
|
|
1051
|
+
featuredImage: p2.featuredImage ? { url: p2.featuredImage.url, altText: p2.featuredImage.altText } : null,
|
|
1052
|
+
hasOnlyDefaultVariant: true,
|
|
1053
|
+
collections: []
|
|
1054
|
+
}));
|
|
1055
|
+
},
|
|
1056
|
+
async getBoxVariants({ ids, subscriptionId }) {
|
|
1057
|
+
const params = new URLSearchParams();
|
|
1058
|
+
for (const id of ids) params.append("ids", id);
|
|
1059
|
+
params.set("subscriptionId", subscriptionId);
|
|
1060
|
+
const res = await fetcher(`/customer/products/variants?${params.toString()}`);
|
|
1061
|
+
if (!res.ok) return [];
|
|
1062
|
+
const data = await res.json();
|
|
1063
|
+
return (data ?? []).map((p2) => {
|
|
1064
|
+
var _a;
|
|
1065
|
+
return {
|
|
1066
|
+
id: p2.id,
|
|
1067
|
+
title: p2.title,
|
|
1068
|
+
description: p2.description ?? "",
|
|
1069
|
+
featuredImage: p2.featuredImage,
|
|
1070
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1071
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1072
|
+
id: v2.id,
|
|
1073
|
+
availableForSale: v2.availableForSale,
|
|
1074
|
+
title: v2.title,
|
|
1075
|
+
currentPrice: v2.currentPrice,
|
|
1076
|
+
basePrice: v2.basePrice,
|
|
1077
|
+
image: v2.image,
|
|
1078
|
+
recurring: v2.recurring
|
|
1079
|
+
}))
|
|
1080
|
+
};
|
|
1081
|
+
});
|
|
1082
|
+
},
|
|
1083
|
+
async getAvailableProducts({ subscriptionId, lineId, q, mode, action, pagination }) {
|
|
1084
|
+
const params = new URLSearchParams();
|
|
1085
|
+
params.set("action", action);
|
|
1086
|
+
if (q) params.set("q", q);
|
|
1087
|
+
if (mode) params.set("mode", mode);
|
|
1088
|
+
if (pagination && "after" in pagination) params.set("after", pagination.after);
|
|
1089
|
+
if (pagination && "before" in pagination) params.set("before", pagination.before);
|
|
1090
|
+
const res = await fetcher(
|
|
1091
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
|
|
1092
|
+
);
|
|
1093
|
+
if (!res.ok) return [];
|
|
1094
|
+
const data = await res.json();
|
|
1095
|
+
return (data ?? []).map((p2) => {
|
|
1096
|
+
var _a;
|
|
1097
|
+
return {
|
|
1098
|
+
id: p2.id,
|
|
1099
|
+
title: p2.title,
|
|
1100
|
+
description: p2.description ?? "",
|
|
1101
|
+
featuredImage: p2.featuredImage,
|
|
1102
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1103
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1104
|
+
id: v2.id,
|
|
1105
|
+
availableForSale: v2.availableForSale,
|
|
1106
|
+
title: v2.title,
|
|
1107
|
+
currentPrice: v2.currentPrice,
|
|
1108
|
+
basePrice: v2.basePrice,
|
|
1109
|
+
image: v2.image,
|
|
1110
|
+
recurring: v2.recurring
|
|
1111
|
+
}))
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
},
|
|
1115
|
+
async getBox({ id }) {
|
|
1116
|
+
const res = await fetcher(`/customer/boxes/${id}`);
|
|
1117
|
+
if (!res.ok) return null;
|
|
1118
|
+
const data = await res.json();
|
|
1119
|
+
return { id, ...data };
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
771
1123
|
const ProductServiceContext = createContext("services/product");
|
|
772
1124
|
function createProductService(adapter) {
|
|
773
1125
|
return {
|
|
@@ -779,7 +1131,11 @@ function createProductService(adapter) {
|
|
|
779
1131
|
},
|
|
780
1132
|
async search(opts) {
|
|
781
1133
|
return adapter.search(opts);
|
|
782
|
-
}
|
|
1134
|
+
},
|
|
1135
|
+
getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
|
|
1136
|
+
getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
|
|
1137
|
+
getAvailableProducts: (params) => adapter.getAvailableProducts(params),
|
|
1138
|
+
getBox: (id) => adapter.getBox({ id })
|
|
783
1139
|
};
|
|
784
1140
|
}
|
|
785
1141
|
function routeNameTableFromPages(pages) {
|
|
@@ -833,7 +1189,85 @@ function createMockRouterService() {
|
|
|
833
1189
|
}
|
|
834
1190
|
}).routerService;
|
|
835
1191
|
}
|
|
836
|
-
|
|
1192
|
+
const DISCOUNT_REJECTION_REASONS = [
|
|
1193
|
+
"INVALID_CODE",
|
|
1194
|
+
"INACTIVE",
|
|
1195
|
+
"NOT_APPLICABLE_TO_CUSTOMER",
|
|
1196
|
+
"ALREADY_ADDED",
|
|
1197
|
+
"ALREADY_USED",
|
|
1198
|
+
"NOT_APPLICABLE_TO_PRODUCTS",
|
|
1199
|
+
"NOT_APPLICABLE_TO_SUBSCRIPTION",
|
|
1200
|
+
"INVALID_COMBINATION",
|
|
1201
|
+
"QUANTITY_REQUIREMENT_NOT_MET"
|
|
1202
|
+
];
|
|
1203
|
+
function toAttributeArray(next) {
|
|
1204
|
+
return Object.entries(next).map(([key, value]) => ({ key, value }));
|
|
1205
|
+
}
|
|
1206
|
+
function createMockSubscriptionAdapter(options = {}) {
|
|
1207
|
+
const richBase = (id, itemId, renewalId) => ({
|
|
1208
|
+
id,
|
|
1209
|
+
customerId: "customer_1",
|
|
1210
|
+
items: [
|
|
1211
|
+
{
|
|
1212
|
+
id: itemId,
|
|
1213
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1214
|
+
// sample-3 "Gentle deodorant", variant sample-3-1 "Green tea and jasmine",
|
|
1215
|
+
// recurring price 5.36 EUR (10% off → 4.82 with SAVE10).
|
|
1216
|
+
productId: "sample-3",
|
|
1217
|
+
variantId: "sample-3-1",
|
|
1218
|
+
quantity: 2,
|
|
1219
|
+
price: { amount: "5.36", currencyCode: "EUR" },
|
|
1220
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1221
|
+
isUpsell: false,
|
|
1222
|
+
isVirtual: false,
|
|
1223
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1224
|
+
currentPrice: { amount: "5.36", currencyCode: "EUR" },
|
|
1225
|
+
lineDiscountedPrice: { amount: "4.82", currencyCode: "EUR" },
|
|
1226
|
+
variantAvailableForSale: true,
|
|
1227
|
+
title: "Gentle deodorant for underarms and bust",
|
|
1228
|
+
variantTitle: "Green tea and jasmine",
|
|
1229
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+3a" },
|
|
1230
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1231
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
|
|
1232
|
+
}
|
|
1233
|
+
],
|
|
1234
|
+
discounts: [
|
|
1235
|
+
{
|
|
1236
|
+
id: "discount_1",
|
|
1237
|
+
type: "percentage",
|
|
1238
|
+
value: 10,
|
|
1239
|
+
code: "SAVE10",
|
|
1240
|
+
description: "10% off subscription"
|
|
1241
|
+
}
|
|
1242
|
+
],
|
|
1243
|
+
status: "active",
|
|
1244
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1245
|
+
frequency: "1 MONTH",
|
|
1246
|
+
createdAt: "2024-10-01T00:00:00Z",
|
|
1247
|
+
updatedAt: "2024-10-14T00:00:00Z",
|
|
1248
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1249
|
+
deliveryPolicy: {
|
|
1250
|
+
intervalCount: 1,
|
|
1251
|
+
interval: "MONTH",
|
|
1252
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1253
|
+
},
|
|
1254
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1255
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1256
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1257
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1258
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1259
|
+
customerPaymentMethod: {
|
|
1260
|
+
id: "pm_1",
|
|
1261
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1262
|
+
active: true,
|
|
1263
|
+
isRecurring: true
|
|
1264
|
+
},
|
|
1265
|
+
requiredBillingCyclesLeft: 3,
|
|
1266
|
+
renewal: { id: renewalId, isInActiveProcessState: false },
|
|
1267
|
+
lastPaymentStatus: "succeeded",
|
|
1268
|
+
pausePeriod: null,
|
|
1269
|
+
isEditable: true
|
|
1270
|
+
});
|
|
837
1271
|
const mockSubscriptions = [
|
|
838
1272
|
{
|
|
839
1273
|
id: "sub_1",
|
|
@@ -841,14 +1275,27 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
841
1275
|
items: [
|
|
842
1276
|
{
|
|
843
1277
|
id: "item_1",
|
|
844
|
-
|
|
845
|
-
|
|
1278
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1279
|
+
// sample-1 "Hydrating Gel Cleanser" (default variant), recurring price
|
|
1280
|
+
// 9.99 EUR (10% off → 8.99 with SAVE10).
|
|
1281
|
+
productId: "sample-1",
|
|
1282
|
+
variantId: "sample-1-1",
|
|
846
1283
|
quantity: 2,
|
|
847
|
-
price: { amount: "
|
|
1284
|
+
price: { amount: "9.99", currencyCode: "EUR" },
|
|
848
1285
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1286
|
+
isUpsell: false,
|
|
1287
|
+
isVirtual: false,
|
|
1288
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1289
|
+
currentPrice: { amount: "9.99", currencyCode: "EUR" },
|
|
1290
|
+
lineDiscountedPrice: { amount: "8.99", currencyCode: "EUR" },
|
|
1291
|
+
variantAvailableForSale: true,
|
|
1292
|
+
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
1293
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+1" },
|
|
1294
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1295
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
849
1296
|
customAttributes: [
|
|
850
|
-
{ key: "
|
|
851
|
-
{ key: "
|
|
1297
|
+
{ key: "skin_type", value: "combination" },
|
|
1298
|
+
{ key: "fragrance", value: "fragrance_free" }
|
|
852
1299
|
]
|
|
853
1300
|
}
|
|
854
1301
|
],
|
|
@@ -866,9 +1313,31 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
866
1313
|
frequency: "1 MONTH",
|
|
867
1314
|
createdAt: "2024-10-01T00:00:00Z",
|
|
868
1315
|
updatedAt: "2024-10-14T00:00:00Z",
|
|
1316
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1317
|
+
deliveryPolicy: {
|
|
1318
|
+
intervalCount: 1,
|
|
1319
|
+
interval: "MONTH",
|
|
1320
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1321
|
+
},
|
|
1322
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1323
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1324
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1325
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1326
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1327
|
+
customerPaymentMethod: {
|
|
1328
|
+
id: "pm_1",
|
|
1329
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1330
|
+
active: true,
|
|
1331
|
+
isRecurring: true
|
|
1332
|
+
},
|
|
1333
|
+
requiredBillingCyclesLeft: 3,
|
|
1334
|
+
renewal: { id: "ren_1", isInActiveProcessState: false },
|
|
1335
|
+
lastPaymentStatus: "succeeded",
|
|
1336
|
+
pausePeriod: null,
|
|
1337
|
+
isEditable: true,
|
|
869
1338
|
customAttributes: [
|
|
870
1339
|
{ key: "subscription_tier", value: "premium" },
|
|
871
|
-
{ key: "gift_message", value: "Enjoy your
|
|
1340
|
+
{ key: "gift_message", value: "Enjoy your skincare routine!" }
|
|
872
1341
|
]
|
|
873
1342
|
},
|
|
874
1343
|
{
|
|
@@ -877,11 +1346,17 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
877
1346
|
items: [
|
|
878
1347
|
{
|
|
879
1348
|
id: "item_2",
|
|
880
|
-
|
|
881
|
-
|
|
1349
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1350
|
+
// sample-2 "More COLLAGEN" (default variant), recurring price 10.99 EUR.
|
|
1351
|
+
productId: "sample-2",
|
|
1352
|
+
variantId: "sample-2-1",
|
|
882
1353
|
quantity: 1,
|
|
883
|
-
price: { amount: "
|
|
1354
|
+
price: { amount: "10.99", currencyCode: "EUR" },
|
|
884
1355
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
|
|
1356
|
+
title: "More COLLAGEN",
|
|
1357
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+2" },
|
|
1358
|
+
billingPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
1359
|
+
deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
885
1360
|
customAttributes: []
|
|
886
1361
|
}
|
|
887
1362
|
],
|
|
@@ -892,17 +1367,66 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
892
1367
|
createdAt: "2024-09-15T00:00:00Z",
|
|
893
1368
|
updatedAt: "2024-10-10T00:00:00Z",
|
|
894
1369
|
customAttributes: []
|
|
1370
|
+
},
|
|
1371
|
+
// Per-status fixtures: groundwork for the deferred editor preview picker
|
|
1372
|
+
// (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
|
|
1373
|
+
// Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
|
|
1374
|
+
{
|
|
1375
|
+
...richBase("sub_paused", "item_paused", "ren_paused"),
|
|
1376
|
+
status: "paused",
|
|
1377
|
+
pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
...richBase("sub_canceled", "item_canceled", "ren_canceled"),
|
|
1381
|
+
status: "canceled",
|
|
1382
|
+
pausePeriod: null
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
...richBase("sub_failed", "item_failed", "ren_failed"),
|
|
1386
|
+
status: "failed",
|
|
1387
|
+
lastPaymentStatus: "failed",
|
|
1388
|
+
pausePeriod: null
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
...richBase("sub_expired", "item_expired", "ren_expired"),
|
|
1392
|
+
status: "expired",
|
|
1393
|
+
pausePeriod: null
|
|
1394
|
+
}
|
|
1395
|
+
];
|
|
1396
|
+
const mockUpsellProducts = [
|
|
1397
|
+
{
|
|
1398
|
+
id: "upsell-1",
|
|
1399
|
+
variantId: "upsell-v-1",
|
|
1400
|
+
title: "Retinol Night Cream",
|
|
1401
|
+
imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
|
|
1402
|
+
price: { amount: "38.00", currencyCode: "USD" },
|
|
1403
|
+
description: "Anti-aging night treatment"
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
id: "upsell-2",
|
|
1407
|
+
variantId: "upsell-v-2",
|
|
1408
|
+
title: "SPF 50 Day Shield",
|
|
1409
|
+
imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
|
|
1410
|
+
price: { amount: "24.00", currencyCode: "USD" },
|
|
1411
|
+
description: "Lightweight daily sun protection"
|
|
895
1412
|
}
|
|
896
1413
|
];
|
|
897
1414
|
return {
|
|
898
1415
|
async getById(id) {
|
|
899
1416
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
900
|
-
const subscription = mockSubscriptions.find((sub) => sub.id === id)
|
|
901
|
-
return {
|
|
1417
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === id);
|
|
1418
|
+
return {
|
|
1419
|
+
_tag: "Success",
|
|
1420
|
+
data: subscription ? {
|
|
1421
|
+
...subscription,
|
|
1422
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1423
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1424
|
+
} : null
|
|
1425
|
+
};
|
|
902
1426
|
},
|
|
903
1427
|
async search(options2 = {}) {
|
|
904
1428
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
905
|
-
const pageSize =
|
|
1429
|
+
const pageSize = 2;
|
|
906
1430
|
const allItems = mockSubscriptions;
|
|
907
1431
|
let startIndex = 0;
|
|
908
1432
|
let endIndex = pageSize;
|
|
@@ -918,29 +1442,108 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
918
1442
|
}
|
|
919
1443
|
}
|
|
920
1444
|
const pageItems = allItems.slice(startIndex, endIndex);
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
items:
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1445
|
+
return {
|
|
1446
|
+
_tag: "Success",
|
|
1447
|
+
data: {
|
|
1448
|
+
items: pageItems.map((sub) => ({
|
|
1449
|
+
...sub,
|
|
1450
|
+
items: sub.items.map((item) => ({ ...item })),
|
|
1451
|
+
discounts: sub.discounts.map((discount) => ({ ...discount }))
|
|
1452
|
+
})),
|
|
1453
|
+
pageInfo: {
|
|
1454
|
+
hasNextPage: endIndex < allItems.length,
|
|
1455
|
+
hasPrevPage: startIndex > 0,
|
|
1456
|
+
startCursor: pageItems.length > 0 ? startIndex.toString() : null,
|
|
1457
|
+
endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
|
|
1458
|
+
}
|
|
932
1459
|
}
|
|
933
1460
|
};
|
|
934
|
-
return { _tag: "Success", data: paginatedList };
|
|
935
1461
|
},
|
|
936
|
-
async addItem() {
|
|
1462
|
+
async addItem({ subscriptionId, item }) {
|
|
1463
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1464
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1465
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1466
|
+
if (!subscription) {
|
|
1467
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1468
|
+
}
|
|
1469
|
+
const currencyCode = ((_a = subscription.items[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD";
|
|
1470
|
+
const product = await ((_b = options.productAdapter) == null ? void 0 : _b.getByVariantId(item.variantId)) ?? await ((_c = options.productAdapter) == null ? void 0 : _c.getById(item.variantId)) ?? null;
|
|
1471
|
+
const variant = (product == null ? void 0 : product.variants.items.find((v2) => v2.id === item.variantId)) ?? (product == null ? void 0 : product.variants.items[0]);
|
|
1472
|
+
const linePrice = ((_e = (_d = variant == null ? void 0 : variant.purchaseOptions) == null ? void 0 : _d[0]) == null ? void 0 : _e.price) ?? (variant == null ? void 0 : variant.price);
|
|
1473
|
+
subscription.items.push({
|
|
1474
|
+
id: `item_${Date.now()}_${subscription.items.length}`,
|
|
1475
|
+
productId: (product == null ? void 0 : product.id) ?? "",
|
|
1476
|
+
variantId: item.variantId,
|
|
1477
|
+
quantity: item.quantity,
|
|
1478
|
+
price: linePrice ? { ...linePrice } : { amount: "0.00", currencyCode },
|
|
1479
|
+
...linePrice ? { currentPrice: { ...linePrice } } : {},
|
|
1480
|
+
title: (product == null ? void 0 : product.title) ?? `Product ${item.variantId}`,
|
|
1481
|
+
...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
|
|
1482
|
+
...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
|
|
1483
|
+
variantAvailableForSale: true,
|
|
1484
|
+
billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
|
|
1485
|
+
deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
|
|
1486
|
+
});
|
|
1487
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1488
|
+
return { _tag: "Success", data: void 0 };
|
|
1489
|
+
},
|
|
1490
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
1491
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1492
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1493
|
+
if (!subscription) {
|
|
1494
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1495
|
+
}
|
|
1496
|
+
subscription.items = subscription.items.filter((i2) => i2.id !== itemId);
|
|
1497
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1498
|
+
return { _tag: "Success", data: void 0 };
|
|
1499
|
+
},
|
|
1500
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1501
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1502
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1503
|
+
if (!subscription) {
|
|
1504
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1505
|
+
}
|
|
1506
|
+
const item = subscription.items.find((i2) => i2.id === lineId);
|
|
1507
|
+
if (!item) {
|
|
1508
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1509
|
+
}
|
|
1510
|
+
if (update.variantId !== void 0) item.variantId = update.variantId;
|
|
1511
|
+
if (update.quantity !== void 0) item.quantity = update.quantity;
|
|
1512
|
+
if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
|
|
1513
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1514
|
+
return { _tag: "Success", data: void 0 };
|
|
1515
|
+
},
|
|
1516
|
+
async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
|
|
1517
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1518
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1519
|
+
if (!subscription) {
|
|
1520
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1521
|
+
}
|
|
1522
|
+
const item = subscription.items.find((i2) => i2.id === lineId);
|
|
1523
|
+
if (!item) {
|
|
1524
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1525
|
+
}
|
|
1526
|
+
item.customAttributes = toAttributeArray(customAttributes);
|
|
1527
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1528
|
+
return { _tag: "Success", data: void 0 };
|
|
1529
|
+
},
|
|
1530
|
+
async updateAttributes({ subscriptionId, customAttributes }) {
|
|
937
1531
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1532
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1533
|
+
if (!subscription) {
|
|
1534
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1535
|
+
}
|
|
1536
|
+
subscription.customAttributes = toAttributeArray(customAttributes);
|
|
1537
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
938
1538
|
return { _tag: "Success", data: void 0 };
|
|
939
1539
|
},
|
|
940
|
-
async
|
|
1540
|
+
async skipLineItem() {
|
|
941
1541
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
942
1542
|
return { _tag: "Success", data: void 0 };
|
|
943
1543
|
},
|
|
1544
|
+
async updateBoxItems() {
|
|
1545
|
+
return { _tag: "Success", data: void 0 };
|
|
1546
|
+
},
|
|
944
1547
|
async addDiscount() {
|
|
945
1548
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
946
1549
|
const behavior = options.addDiscountBehavior ?? { kind: "success" };
|
|
@@ -969,24 +1572,29 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
969
1572
|
(sub) => sub.id === params.subscriptionId
|
|
970
1573
|
);
|
|
971
1574
|
if (!subscription) {
|
|
972
|
-
return {
|
|
973
|
-
_tag: "Failure",
|
|
974
|
-
error: "Subscription not found"
|
|
975
|
-
};
|
|
1575
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
976
1576
|
}
|
|
977
1577
|
subscription.nextDeliveryDate = new Date(params.newDate);
|
|
978
1578
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
979
|
-
subscription
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1579
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1580
|
+
},
|
|
1581
|
+
async updateCustomAttributes(params) {
|
|
1582
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1583
|
+
const subscription = mockSubscriptions.find(
|
|
1584
|
+
(sub) => sub.id === params.subscriptionId
|
|
1585
|
+
);
|
|
1586
|
+
if (!subscription) {
|
|
1587
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1588
|
+
}
|
|
1589
|
+
const preserved = (subscription.customAttributes ?? []).filter(
|
|
1590
|
+
(attr) => attr.key.startsWith("_")
|
|
1591
|
+
);
|
|
1592
|
+
const next = Object.entries(params.customAttributes).map(
|
|
1593
|
+
([key, value]) => ({ key, value })
|
|
1594
|
+
);
|
|
1595
|
+
subscription.customAttributes = [...preserved, ...next];
|
|
1596
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1597
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
990
1598
|
},
|
|
991
1599
|
async resume(params) {
|
|
992
1600
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -994,21 +1602,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
994
1602
|
(sub) => sub.id === params.subscriptionId
|
|
995
1603
|
);
|
|
996
1604
|
if (!subscription) {
|
|
997
|
-
return {
|
|
998
|
-
_tag: "Failure",
|
|
999
|
-
error: "Subscription not found"
|
|
1000
|
-
};
|
|
1605
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1001
1606
|
}
|
|
1002
1607
|
subscription.status = "active";
|
|
1003
1608
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
|
-
return {
|
|
1005
|
-
_tag: "Success",
|
|
1006
|
-
data: {
|
|
1007
|
-
...subscription,
|
|
1008
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1009
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1609
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1012
1610
|
},
|
|
1013
1611
|
async pause(params) {
|
|
1014
1612
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1016,21 +1614,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1016
1614
|
(sub) => sub.id === params.subscriptionId
|
|
1017
1615
|
);
|
|
1018
1616
|
if (!subscription) {
|
|
1019
|
-
return {
|
|
1020
|
-
_tag: "Failure",
|
|
1021
|
-
error: "Subscription not found"
|
|
1022
|
-
};
|
|
1617
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1023
1618
|
}
|
|
1024
1619
|
subscription.status = "paused";
|
|
1025
1620
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1026
|
-
return {
|
|
1027
|
-
_tag: "Success",
|
|
1028
|
-
data: {
|
|
1029
|
-
...subscription,
|
|
1030
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1031
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1621
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1034
1622
|
},
|
|
1035
1623
|
async reactivate(params) {
|
|
1036
1624
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1038,21 +1626,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1038
1626
|
(sub) => sub.id === params.subscriptionId
|
|
1039
1627
|
);
|
|
1040
1628
|
if (!subscription) {
|
|
1041
|
-
return {
|
|
1042
|
-
_tag: "Failure",
|
|
1043
|
-
error: "Subscription not found"
|
|
1044
|
-
};
|
|
1629
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1045
1630
|
}
|
|
1046
1631
|
subscription.status = "active";
|
|
1047
1632
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1048
|
-
return {
|
|
1049
|
-
_tag: "Success",
|
|
1050
|
-
data: {
|
|
1051
|
-
...subscription,
|
|
1052
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1053
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
1633
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1056
1634
|
},
|
|
1057
1635
|
async cancel(params) {
|
|
1058
1636
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1060,21 +1638,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1060
1638
|
(sub) => sub.id === params.subscriptionId
|
|
1061
1639
|
);
|
|
1062
1640
|
if (!subscription) {
|
|
1063
|
-
return {
|
|
1064
|
-
_tag: "Failure",
|
|
1065
|
-
error: "Subscription not found"
|
|
1066
|
-
};
|
|
1641
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1067
1642
|
}
|
|
1068
1643
|
subscription.status = "canceled";
|
|
1069
1644
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1070
|
-
return {
|
|
1071
|
-
_tag: "Success",
|
|
1072
|
-
data: {
|
|
1073
|
-
...subscription,
|
|
1074
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1075
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1645
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1078
1646
|
},
|
|
1079
1647
|
async getAvailablePlans() {
|
|
1080
1648
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1102,128 +1670,1460 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1102
1670
|
]
|
|
1103
1671
|
};
|
|
1104
1672
|
},
|
|
1673
|
+
async getLastOrder(params) {
|
|
1674
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1675
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1676
|
+
if (!subscription) return { _tag: "Success", data: null };
|
|
1677
|
+
return {
|
|
1678
|
+
_tag: "Success",
|
|
1679
|
+
data: {
|
|
1680
|
+
id: `mock_last_order_${params.subscriptionId}`,
|
|
1681
|
+
createdAt: "2026-04-22T12:00:00Z"
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
},
|
|
1685
|
+
async renewNow(_params) {
|
|
1686
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1687
|
+
return { _tag: "Success", data: { result: "success" } };
|
|
1688
|
+
},
|
|
1689
|
+
async changeFrequency(params) {
|
|
1690
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1691
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1692
|
+
if (!subscription) {
|
|
1693
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1694
|
+
}
|
|
1695
|
+
if (typeof params.plan === "string") {
|
|
1696
|
+
subscription.frequency = `1 ${params.plan}`;
|
|
1697
|
+
} else {
|
|
1698
|
+
subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
|
|
1699
|
+
if (subscription.billingPolicy) {
|
|
1700
|
+
subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
|
|
1701
|
+
}
|
|
1702
|
+
if (subscription.deliveryPolicy) {
|
|
1703
|
+
subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1707
|
+
return {
|
|
1708
|
+
_tag: "Success",
|
|
1709
|
+
data: {
|
|
1710
|
+
...subscription,
|
|
1711
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1712
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
},
|
|
1105
1716
|
async getDefaultSubscriptionId() {
|
|
1106
1717
|
var _a;
|
|
1107
1718
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1108
1719
|
return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
|
|
1720
|
+
},
|
|
1721
|
+
async getUpsellProducts() {
|
|
1722
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1723
|
+
return { _tag: "Success", data: [...mockUpsellProducts] };
|
|
1724
|
+
},
|
|
1725
|
+
async addUpsellProduct() {
|
|
1726
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
1727
|
+
return { _tag: "Success", data: void 0 };
|
|
1109
1728
|
}
|
|
1110
1729
|
};
|
|
1111
1730
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (typeof window === "undefined") {
|
|
1115
|
-
return null;
|
|
1116
|
-
}
|
|
1117
|
-
const pathname = window.location.pathname;
|
|
1118
|
-
const segments = pathname.split("/").filter(Boolean);
|
|
1119
|
-
const subscriptionIndex = segments.findIndex(
|
|
1120
|
-
(segment) => segment === "subscriptions" || segment === "subscription"
|
|
1121
|
-
);
|
|
1122
|
-
if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
|
|
1123
|
-
return segments[subscriptionIndex + 1];
|
|
1124
|
-
}
|
|
1125
|
-
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1126
|
-
const lastSegment = segments[segments.length - 1];
|
|
1127
|
-
if (lastSegment && uuidPattern.test(lastSegment)) {
|
|
1128
|
-
return lastSegment;
|
|
1129
|
-
}
|
|
1130
|
-
return null;
|
|
1731
|
+
function normalizeInstrumentType(type) {
|
|
1732
|
+
return type.replace(/-/g, "_");
|
|
1131
1733
|
}
|
|
1132
|
-
function
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
async function refreshCurrent(subscriptionId) {
|
|
1137
|
-
const result = await adapter.getById(subscriptionId);
|
|
1138
|
-
if (result._tag === "Success" && result.data != null) {
|
|
1139
|
-
current.value = result.data;
|
|
1140
|
-
}
|
|
1734
|
+
function mapPaymentMethod(rawPm) {
|
|
1735
|
+
if (!rawPm) return null;
|
|
1736
|
+
if (typeof rawPm === "string") {
|
|
1737
|
+
return { id: rawPm };
|
|
1141
1738
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
itemsChangedAtSignal.value = itemsChangedAtSignal.value + 1;
|
|
1149
|
-
}
|
|
1150
|
-
function subscriptionIdFromCurrent() {
|
|
1151
|
-
var _a;
|
|
1152
|
-
return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
|
|
1153
|
-
}
|
|
1154
|
-
const service = {
|
|
1155
|
-
search: adapter.search,
|
|
1156
|
-
getById: adapter.getById,
|
|
1157
|
-
getCurrentSubscriptionId,
|
|
1158
|
-
addItem: async (subscriptionId, item) => {
|
|
1159
|
-
const result = await adapter.addItem({ subscriptionId, item });
|
|
1160
|
-
if (result._tag === "Success") {
|
|
1161
|
-
await refreshAfterItemChange(subscriptionId);
|
|
1162
|
-
}
|
|
1163
|
-
return result;
|
|
1164
|
-
},
|
|
1165
|
-
removeItem: async (subscriptionId, itemId) => {
|
|
1166
|
-
const result = await adapter.removeItem({ subscriptionId, itemId });
|
|
1167
|
-
if (result._tag === "Success") {
|
|
1168
|
-
await refreshAfterItemChange(subscriptionId);
|
|
1739
|
+
return {
|
|
1740
|
+
id: rawPm.id ?? "",
|
|
1741
|
+
...rawPm.instrument != null ? {
|
|
1742
|
+
instrument: {
|
|
1743
|
+
...rawPm.instrument,
|
|
1744
|
+
type: normalizeInstrumentType(rawPm.instrument.type)
|
|
1169
1745
|
}
|
|
1170
|
-
|
|
1171
|
-
},
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1746
|
+
} : {},
|
|
1747
|
+
...rawPm.active != null ? { active: rawPm.active } : {},
|
|
1748
|
+
...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
function mapSubscription(raw) {
|
|
1752
|
+
var _a, _b;
|
|
1753
|
+
const status = raw.status === "merged" ? "canceled" : raw.status;
|
|
1754
|
+
const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
|
|
1755
|
+
const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
|
|
1756
|
+
return {
|
|
1757
|
+
id: raw.id,
|
|
1758
|
+
customerId: "",
|
|
1759
|
+
serial: raw.serial,
|
|
1760
|
+
items: (raw.items ?? []).map(
|
|
1761
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1762
|
+
(item) => ({
|
|
1763
|
+
id: item.id,
|
|
1764
|
+
productId: item.productId ?? item.product ?? "",
|
|
1765
|
+
variantId: item.variantId ?? item.variant ?? "",
|
|
1766
|
+
quantity: item.quantity,
|
|
1767
|
+
price: item.price ?? {
|
|
1768
|
+
amount: String(item.totalPrice ?? 0),
|
|
1769
|
+
currencyCode: raw.currencyCode ?? ""
|
|
1770
|
+
},
|
|
1771
|
+
nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
|
|
1772
|
+
billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
|
|
1773
|
+
deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
|
|
1774
|
+
// Map human-readable titles from the v1 SubscriptionItemResource
|
|
1775
|
+
title: item.title,
|
|
1776
|
+
variantTitle: item.subtitle ?? void 0,
|
|
1777
|
+
// recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
|
|
1778
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
1779
|
+
customAttributes: Array.isArray(item.customAttributes) ? item.customAttributes : []
|
|
1780
|
+
})
|
|
1781
|
+
),
|
|
1782
|
+
discounts: (raw.discounts ?? []).map(
|
|
1783
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1784
|
+
(d3) => {
|
|
1785
|
+
var _a2, _b2, _c;
|
|
1175
1786
|
return {
|
|
1176
|
-
|
|
1177
|
-
|
|
1787
|
+
id: d3.id,
|
|
1788
|
+
type: ((_a2 = d3.value) == null ? void 0 : _a2.type) === "fixed-amount" || d3.type === "fixed_amount" ? "fixed_amount" : "percentage",
|
|
1789
|
+
value: ((_b2 = d3.value) == null ? void 0 : _b2.amount) ?? ((_c = d3.value) == null ? void 0 : _c.percentage) ?? d3.value ?? 0,
|
|
1790
|
+
code: d3.title ?? d3.code ?? "",
|
|
1791
|
+
description: d3.description ?? ""
|
|
1178
1792
|
};
|
|
1179
1793
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1794
|
+
),
|
|
1795
|
+
status,
|
|
1796
|
+
nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
|
|
1797
|
+
// Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
|
|
1798
|
+
nextBillingDate: raw.nextBillingDate ?? null,
|
|
1799
|
+
// Map customerPaymentMethod — expanded to an object (id + instrument) via the
|
|
1800
|
+
// adapter's ?expand=paymentMethod request; mapPaymentMethod tolerates a bare id too.
|
|
1801
|
+
customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
|
|
1802
|
+
frequency,
|
|
1803
|
+
createdAt: raw.createdAt,
|
|
1804
|
+
updatedAt: raw.updatedAt,
|
|
1805
|
+
billingPolicy: raw.billingPolicy ?? null,
|
|
1806
|
+
deliveryPolicy: raw.deliveryPolicy ?? null,
|
|
1807
|
+
customAttributes: Array.isArray(raw.customAttributes) ? raw.customAttributes : []
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
async function readErrorBody(res) {
|
|
1811
|
+
try {
|
|
1812
|
+
const body = await res.json();
|
|
1813
|
+
return body != null && typeof body === "object" ? body : {};
|
|
1814
|
+
} catch {
|
|
1815
|
+
return {};
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
function createApiSubscriptionAdapter(fetcher) {
|
|
1819
|
+
const adapter = {
|
|
1820
|
+
async search(options) {
|
|
1821
|
+
var _a;
|
|
1822
|
+
try {
|
|
1823
|
+
const params = new URLSearchParams();
|
|
1824
|
+
if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
|
|
1825
|
+
const groups = options.filters.filter((f) => Object.keys(f).length > 0).map((f) => Object.entries(f).filter(([, v2]) => Boolean(v2))).filter((entries) => entries.length > 0).map(
|
|
1826
|
+
(entries) => "(" + entries.map(([k, v2]) => `${k}:'${v2}'`).join(" AND ") + ")"
|
|
1827
|
+
);
|
|
1828
|
+
if (groups.length) params.set("query", groups.join(" OR "));
|
|
1829
|
+
}
|
|
1830
|
+
if (options == null ? void 0 : options.includeCanceledItems) {
|
|
1831
|
+
params.set("includeCanceledItems", "true");
|
|
1832
|
+
}
|
|
1833
|
+
params.set("expand", "paymentMethod");
|
|
1834
|
+
if (options == null ? void 0 : options.pagination) {
|
|
1835
|
+
if ("after" in options.pagination)
|
|
1836
|
+
params.set("after", options.pagination.after);
|
|
1837
|
+
else if ("before" in options.pagination)
|
|
1838
|
+
params.set("before", options.pagination.before);
|
|
1839
|
+
}
|
|
1840
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1841
|
+
const res = await fetcher(`/customer/v1/subscriptions${query}`);
|
|
1842
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1843
|
+
const body = await res.json();
|
|
1189
1844
|
return {
|
|
1190
|
-
_tag: "
|
|
1191
|
-
|
|
1845
|
+
_tag: "Success",
|
|
1846
|
+
data: {
|
|
1847
|
+
items: body.data.map(mapSubscription),
|
|
1848
|
+
pageInfo: {
|
|
1849
|
+
hasNextPage: body.hasNextPage,
|
|
1850
|
+
hasPrevPage: body.hasPrevPage,
|
|
1851
|
+
startCursor: body.startCursor,
|
|
1852
|
+
endCursor: body.endCursor
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1192
1855
|
};
|
|
1856
|
+
} catch (e2) {
|
|
1857
|
+
return { _tag: "Failure", error: e2 };
|
|
1858
|
+
}
|
|
1859
|
+
},
|
|
1860
|
+
async getById(id) {
|
|
1861
|
+
try {
|
|
1862
|
+
const res = await fetcher(
|
|
1863
|
+
`/customer/v1/subscriptions?query=id:"${id}"&limit=1&expand=paymentMethod`
|
|
1864
|
+
);
|
|
1865
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1866
|
+
const body = await res.json();
|
|
1867
|
+
const raw = body.data[0];
|
|
1868
|
+
return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
|
|
1869
|
+
} catch (e2) {
|
|
1870
|
+
return { _tag: "Failure", error: e2 };
|
|
1871
|
+
}
|
|
1872
|
+
},
|
|
1873
|
+
async addItem({ subscriptionId, item }) {
|
|
1874
|
+
try {
|
|
1875
|
+
const res = await fetcher(
|
|
1876
|
+
`/customer/v1/subscriptions/${subscriptionId}/items`,
|
|
1877
|
+
{
|
|
1878
|
+
method: "POST",
|
|
1879
|
+
headers: { "Content-Type": "application/json" },
|
|
1880
|
+
body: JSON.stringify({ variant: item.variantId, quantity: item.quantity })
|
|
1881
|
+
}
|
|
1882
|
+
);
|
|
1883
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1884
|
+
return { _tag: "Success", data: void 0 };
|
|
1885
|
+
} catch (e2) {
|
|
1886
|
+
return { _tag: "Failure", error: e2 };
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
1890
|
+
try {
|
|
1891
|
+
const res = await fetcher(
|
|
1892
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
|
|
1893
|
+
{ method: "DELETE" }
|
|
1894
|
+
);
|
|
1895
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1896
|
+
return { _tag: "Success", data: void 0 };
|
|
1897
|
+
} catch (e2) {
|
|
1898
|
+
return { _tag: "Failure", error: e2 };
|
|
1899
|
+
}
|
|
1900
|
+
},
|
|
1901
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1902
|
+
try {
|
|
1903
|
+
const body = {
|
|
1904
|
+
...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
|
|
1905
|
+
...update.quantity !== void 0 ? { quantity: update.quantity } : {},
|
|
1906
|
+
...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
|
|
1907
|
+
...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
|
|
1908
|
+
};
|
|
1909
|
+
const res = await fetcher(
|
|
1910
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1911
|
+
{
|
|
1912
|
+
method: "PUT",
|
|
1913
|
+
headers: { "Content-Type": "application/json" },
|
|
1914
|
+
body: JSON.stringify(body)
|
|
1915
|
+
}
|
|
1916
|
+
);
|
|
1917
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1918
|
+
return { _tag: "Success", data: void 0 };
|
|
1919
|
+
} catch (e2) {
|
|
1920
|
+
return { _tag: "Failure", error: e2 };
|
|
1921
|
+
}
|
|
1922
|
+
},
|
|
1923
|
+
async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
|
|
1924
|
+
try {
|
|
1925
|
+
const res = await fetcher(
|
|
1926
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${lineId}`,
|
|
1927
|
+
{
|
|
1928
|
+
method: "PATCH",
|
|
1929
|
+
headers: { "Content-Type": "application/json" },
|
|
1930
|
+
body: JSON.stringify({ customAttributes })
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1933
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1934
|
+
return { _tag: "Success", data: void 0 };
|
|
1935
|
+
} catch (e2) {
|
|
1936
|
+
return { _tag: "Failure", error: e2 };
|
|
1937
|
+
}
|
|
1938
|
+
},
|
|
1939
|
+
async updateAttributes({ subscriptionId, customAttributes }) {
|
|
1940
|
+
try {
|
|
1941
|
+
const res = await fetcher(
|
|
1942
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
1943
|
+
{
|
|
1944
|
+
method: "PATCH",
|
|
1945
|
+
headers: { "Content-Type": "application/json" },
|
|
1946
|
+
body: JSON.stringify({ customAttributes })
|
|
1947
|
+
}
|
|
1948
|
+
);
|
|
1949
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1950
|
+
return { _tag: "Success", data: void 0 };
|
|
1951
|
+
} catch (e2) {
|
|
1952
|
+
return { _tag: "Failure", error: e2 };
|
|
1953
|
+
}
|
|
1954
|
+
},
|
|
1955
|
+
async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
|
|
1956
|
+
try {
|
|
1957
|
+
const res = await fetcher(
|
|
1958
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1959
|
+
{
|
|
1960
|
+
method: "PUT",
|
|
1961
|
+
headers: { "Content-Type": "application/json" },
|
|
1962
|
+
body: JSON.stringify({ nextBillingDate })
|
|
1963
|
+
}
|
|
1964
|
+
);
|
|
1965
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1966
|
+
return { _tag: "Success", data: void 0 };
|
|
1967
|
+
} catch (e2) {
|
|
1968
|
+
return { _tag: "Failure", error: e2 };
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
|
|
1972
|
+
try {
|
|
1973
|
+
const res = await fetcher(
|
|
1974
|
+
`/customer/subscriptions/${subscriptionId}/update-box-products`,
|
|
1975
|
+
{
|
|
1976
|
+
method: "POST",
|
|
1977
|
+
headers: { "Content-Type": "application/json" },
|
|
1978
|
+
body: JSON.stringify({ parentId, items, shippingMethodHandle })
|
|
1979
|
+
}
|
|
1980
|
+
);
|
|
1981
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1982
|
+
return { _tag: "Success", data: void 0 };
|
|
1983
|
+
} catch (e2) {
|
|
1984
|
+
return { _tag: "Failure", error: e2 };
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
async addDiscount({ subscriptionId, code }) {
|
|
1988
|
+
try {
|
|
1989
|
+
const res = await fetcher(
|
|
1990
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts`,
|
|
1991
|
+
{
|
|
1992
|
+
method: "POST",
|
|
1993
|
+
headers: { "Content-Type": "application/json" },
|
|
1994
|
+
body: JSON.stringify({ code })
|
|
1995
|
+
}
|
|
1996
|
+
);
|
|
1997
|
+
if (!res.ok) {
|
|
1998
|
+
const body = await readErrorBody(res);
|
|
1999
|
+
if (res.status === 400 && body.code === "DISCOUNT_CODE_REJECTED") {
|
|
2000
|
+
const raw = body.rejectionReason;
|
|
2001
|
+
const reason = typeof raw === "string" && DISCOUNT_REJECTION_REASONS.includes(raw) ? raw : "INVALID_CODE";
|
|
2002
|
+
return { _tag: "Failure", error: { kind: "REJECTED", reason } };
|
|
2003
|
+
}
|
|
2004
|
+
if (res.status === 404) {
|
|
2005
|
+
return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
|
|
2006
|
+
}
|
|
2007
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
|
|
2008
|
+
}
|
|
2009
|
+
return { _tag: "Success", data: void 0 };
|
|
2010
|
+
} catch (e2) {
|
|
2011
|
+
return {
|
|
2012
|
+
_tag: "Failure",
|
|
2013
|
+
error: { kind: "NETWORK", message: e2 instanceof Error ? e2.message : void 0 }
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
},
|
|
2017
|
+
async removeDiscount({ subscriptionId, discountId }) {
|
|
2018
|
+
try {
|
|
2019
|
+
const res = await fetcher(
|
|
2020
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
|
|
2021
|
+
{ method: "DELETE" }
|
|
2022
|
+
);
|
|
2023
|
+
if (!res.ok) {
|
|
2024
|
+
if (res.status === 404) {
|
|
2025
|
+
const body = await readErrorBody(res);
|
|
2026
|
+
const message = typeof body.message === "string" ? body.message : "";
|
|
2027
|
+
if (message.toLowerCase().includes("discount")) {
|
|
2028
|
+
return { _tag: "Failure", error: { kind: "DISCOUNT_NOT_FOUND" } };
|
|
2029
|
+
}
|
|
2030
|
+
return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
|
|
2031
|
+
}
|
|
2032
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
|
|
2033
|
+
}
|
|
2034
|
+
return { _tag: "Success", data: void 0 };
|
|
2035
|
+
} catch (e2) {
|
|
2036
|
+
return {
|
|
2037
|
+
_tag: "Failure",
|
|
2038
|
+
error: { kind: "NETWORK", message: e2 instanceof Error ? e2.message : void 0 }
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
},
|
|
2042
|
+
// Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
|
|
2043
|
+
async reschedule({ subscriptionId, newDate }) {
|
|
2044
|
+
try {
|
|
2045
|
+
const res = await fetcher(
|
|
2046
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2047
|
+
{
|
|
2048
|
+
method: "PATCH",
|
|
2049
|
+
headers: { "Content-Type": "application/json" },
|
|
2050
|
+
body: JSON.stringify({ nextBillingDate: newDate })
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2054
|
+
const data = await res.json();
|
|
2055
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2056
|
+
} catch (e2) {
|
|
2057
|
+
return { _tag: "Failure", error: e2 };
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
// PATCH /customer/v1/subscriptions/{id} with the full customer-visible set.
|
|
2061
|
+
async updateCustomAttributes({ subscriptionId, customAttributes }) {
|
|
2062
|
+
try {
|
|
2063
|
+
const res = await fetcher(
|
|
2064
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2065
|
+
{
|
|
2066
|
+
method: "PATCH",
|
|
2067
|
+
headers: { "Content-Type": "application/json" },
|
|
2068
|
+
body: JSON.stringify({ customAttributes })
|
|
2069
|
+
}
|
|
2070
|
+
);
|
|
2071
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2072
|
+
const data = await res.json();
|
|
2073
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2074
|
+
} catch (e2) {
|
|
2075
|
+
return { _tag: "Failure", error: e2 };
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
async resume({ subscriptionId }) {
|
|
2079
|
+
try {
|
|
2080
|
+
const res = await fetcher(
|
|
2081
|
+
`/customer/v1/subscriptions/${subscriptionId}/resume`,
|
|
2082
|
+
{ method: "POST" }
|
|
2083
|
+
);
|
|
2084
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2085
|
+
const data = await res.json();
|
|
2086
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2087
|
+
} catch (e2) {
|
|
2088
|
+
return { _tag: "Failure", error: e2 };
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
async pause({ subscriptionId }) {
|
|
2092
|
+
try {
|
|
2093
|
+
const res = await fetcher(
|
|
2094
|
+
`/customer/v1/subscriptions/${subscriptionId}/pause`,
|
|
2095
|
+
{ method: "POST" }
|
|
2096
|
+
);
|
|
2097
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2098
|
+
const data = await res.json();
|
|
2099
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2100
|
+
} catch (e2) {
|
|
2101
|
+
return { _tag: "Failure", error: e2 };
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
async reactivate({ subscriptionId }) {
|
|
2105
|
+
try {
|
|
2106
|
+
const res = await fetcher(
|
|
2107
|
+
`/customer/v1/subscriptions/${subscriptionId}/reactivate`,
|
|
2108
|
+
{ method: "POST" }
|
|
2109
|
+
);
|
|
2110
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2111
|
+
const data = await res.json();
|
|
2112
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2113
|
+
} catch (e2) {
|
|
2114
|
+
return { _tag: "Failure", error: e2 };
|
|
2115
|
+
}
|
|
2116
|
+
},
|
|
2117
|
+
async cancel({ subscriptionId, reason }) {
|
|
2118
|
+
try {
|
|
2119
|
+
const res = await fetcher(
|
|
2120
|
+
`/customer/v1/subscriptions/${subscriptionId}/cancel`,
|
|
2121
|
+
{
|
|
2122
|
+
method: "POST",
|
|
2123
|
+
headers: { "Content-Type": "application/json" },
|
|
2124
|
+
body: JSON.stringify({ cancellationReason: reason })
|
|
2125
|
+
}
|
|
2126
|
+
);
|
|
2127
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2128
|
+
const data = await res.json();
|
|
2129
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2130
|
+
} catch (e2) {
|
|
2131
|
+
return { _tag: "Failure", error: e2 };
|
|
2132
|
+
}
|
|
2133
|
+
},
|
|
2134
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
|
|
2135
|
+
async getAvailablePlans({ subscriptionId }) {
|
|
2136
|
+
try {
|
|
2137
|
+
const res = await fetcher(
|
|
2138
|
+
`/customer/subscriptions/${subscriptionId}/plans`
|
|
2139
|
+
);
|
|
2140
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2141
|
+
const data = await res.json();
|
|
2142
|
+
return { _tag: "Success", data };
|
|
2143
|
+
} catch (e2) {
|
|
2144
|
+
return { _tag: "Failure", error: e2 };
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2148
|
+
async getDefaultSubscriptionId() {
|
|
2149
|
+
try {
|
|
2150
|
+
const res = await fetcher(
|
|
2151
|
+
`/customer/subscriptions/one-by-next-billing-date`
|
|
2152
|
+
);
|
|
2153
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2154
|
+
const data = await res.json();
|
|
2155
|
+
return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
|
|
2156
|
+
} catch (e2) {
|
|
2157
|
+
return { _tag: "Failure", error: e2 };
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2160
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
|
|
2161
|
+
async getUpsellProducts({ subscriptionId }) {
|
|
2162
|
+
try {
|
|
2163
|
+
const res = await fetcher(
|
|
2164
|
+
`/customer/subscriptions/${subscriptionId}/upsell-products`
|
|
2165
|
+
);
|
|
2166
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2167
|
+
const data = await res.json();
|
|
2168
|
+
const products = data.map((p2) => {
|
|
2169
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2170
|
+
return {
|
|
2171
|
+
id: p2.id,
|
|
2172
|
+
variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
|
|
2173
|
+
title: p2.title ?? "",
|
|
2174
|
+
imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
|
|
2175
|
+
price: {
|
|
2176
|
+
amount: String(((_f = (_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
|
|
2177
|
+
currencyCode: ((_i = (_h = (_g = p2.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
|
|
2178
|
+
},
|
|
2179
|
+
// recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
|
|
2180
|
+
recurring: (_k = (_j = p2.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
|
|
2181
|
+
};
|
|
2182
|
+
});
|
|
2183
|
+
return { _tag: "Success", data: products };
|
|
2184
|
+
} catch (e2) {
|
|
2185
|
+
return { _tag: "Failure", error: e2 };
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2188
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
|
|
2189
|
+
async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
|
|
2190
|
+
try {
|
|
2191
|
+
const res = await fetcher(
|
|
2192
|
+
`/customer/subscriptions/${subscriptionId}/upsell`,
|
|
2193
|
+
{
|
|
2194
|
+
method: "POST",
|
|
2195
|
+
headers: { "Content-Type": "application/json" },
|
|
2196
|
+
body: JSON.stringify({
|
|
2197
|
+
productVariantId: variantId,
|
|
2198
|
+
quantity,
|
|
2199
|
+
...oneTime ? { renewalLimit: 1 } : {}
|
|
2200
|
+
})
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2204
|
+
return { _tag: "Success", data: void 0 };
|
|
2205
|
+
} catch (e2) {
|
|
2206
|
+
return { _tag: "Failure", error: e2 };
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2210
|
+
async getLastOrder({ subscriptionId }) {
|
|
2211
|
+
try {
|
|
2212
|
+
const res = await fetcher(
|
|
2213
|
+
`/customer/subscriptions/${subscriptionId}/last-order`
|
|
2214
|
+
);
|
|
2215
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2216
|
+
const data = await res.json();
|
|
2217
|
+
if (!data) return { _tag: "Success", data: null };
|
|
2218
|
+
return {
|
|
2219
|
+
_tag: "Success",
|
|
2220
|
+
data: { id: data.id, createdAt: data.createdAt }
|
|
2221
|
+
};
|
|
2222
|
+
} catch (e2) {
|
|
2223
|
+
return { _tag: "Failure", error: e2 };
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
// Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
|
|
2227
|
+
// retry → POST /customer/subscriptions/{id}/retry
|
|
2228
|
+
// renewalId → POST /customer/renewals/{renewalId}/renew
|
|
2229
|
+
// default → POST /customer/subscriptions/{id}/order-now
|
|
2230
|
+
async renewNow({ subscriptionId, renewalId, retry }) {
|
|
2231
|
+
try {
|
|
2232
|
+
const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
|
|
2233
|
+
const res = await fetcher(url, { method: "POST" });
|
|
2234
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2235
|
+
const body = res.status === 204 ? null : await res.json();
|
|
2236
|
+
return {
|
|
2237
|
+
_tag: "Success",
|
|
2238
|
+
data: {
|
|
2239
|
+
nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
|
|
2240
|
+
result: (body == null ? void 0 : body.result) ?? "success"
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
} catch (e2) {
|
|
2244
|
+
return { _tag: "Failure", error: e2 };
|
|
2245
|
+
}
|
|
2246
|
+
},
|
|
2247
|
+
// Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
|
|
2248
|
+
async changeFrequency({ subscriptionId, plan }) {
|
|
2249
|
+
try {
|
|
2250
|
+
const res = await fetcher(
|
|
2251
|
+
`/customer/subscriptions/${subscriptionId}/plan`,
|
|
2252
|
+
{
|
|
2253
|
+
method: "POST",
|
|
2254
|
+
headers: { "Content-Type": "application/json" },
|
|
2255
|
+
body: JSON.stringify({ plan })
|
|
2256
|
+
}
|
|
2257
|
+
);
|
|
2258
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2259
|
+
const refreshed = await adapter.getById(subscriptionId);
|
|
2260
|
+
if (refreshed._tag === "Failure") return refreshed;
|
|
2261
|
+
if (refreshed.data) return { _tag: "Success", data: refreshed.data };
|
|
2262
|
+
return {
|
|
2263
|
+
_tag: "Failure",
|
|
2264
|
+
error: "Subscription not found after frequency change"
|
|
2265
|
+
};
|
|
2266
|
+
} catch (e2) {
|
|
2267
|
+
return { _tag: "Failure", error: e2 };
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
return adapter;
|
|
2272
|
+
}
|
|
2273
|
+
function expandableId(field) {
|
|
2274
|
+
if (typeof field === "string") return field;
|
|
2275
|
+
if (field != null && typeof field === "object" && "id" in field) {
|
|
2276
|
+
const id = field.id;
|
|
2277
|
+
return typeof id === "string" ? id : void 0;
|
|
2278
|
+
}
|
|
2279
|
+
return void 0;
|
|
2280
|
+
}
|
|
2281
|
+
function mapScheduleOrder(order) {
|
|
2282
|
+
return {
|
|
2283
|
+
id: order.id,
|
|
2284
|
+
customerId: order.customerId,
|
|
2285
|
+
subscriptionId: order.subscriptionId,
|
|
2286
|
+
date: order.date,
|
|
2287
|
+
skipped: order.skipped ?? false,
|
|
2288
|
+
currencyCode: order.currencyCode,
|
|
2289
|
+
subtotal: order.subtotal,
|
|
2290
|
+
deliveryPrice: order.deliveryPrice,
|
|
2291
|
+
cycleIndex: order.cycleIndex,
|
|
2292
|
+
deliveryDate: order.deliveryDate ?? order.date,
|
|
2293
|
+
status: order.status ?? (order.skipped ? "skipped" : "scheduled"),
|
|
2294
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2295
|
+
items: (order.items ?? []).map((item) => {
|
|
2296
|
+
var _a;
|
|
2297
|
+
return {
|
|
2298
|
+
id: item.id,
|
|
2299
|
+
title: item.title,
|
|
2300
|
+
subtitle: item.subtitle ?? null,
|
|
2301
|
+
imageUrl: item.imageUrl ?? ((_a = item.variantImage) == null ? void 0 : _a.url) ?? void 0,
|
|
2302
|
+
variantId: expandableId(item.variant) ?? "",
|
|
2303
|
+
quantity: item.quantity,
|
|
2304
|
+
price: item.price,
|
|
2305
|
+
totalPrice: item.totalPrice,
|
|
2306
|
+
skipped: item.skipped ?? false,
|
|
2307
|
+
variantImage: item.variantImage != null ? { url: item.variantImage.url ?? "", altText: item.variantImage.altText ?? void 0 } : null,
|
|
2308
|
+
variantAvailableForSale: item.variantAvailableForSale ?? void 0,
|
|
2309
|
+
subscriptionId: expandableId(item.subscription) ?? "",
|
|
2310
|
+
subscriptionLineId: expandableId(item.subscriptionItem) ?? "",
|
|
2311
|
+
productId: expandableId(item.product),
|
|
2312
|
+
parentProductId: item.parentProductId ?? null,
|
|
2313
|
+
parentLineId: item.parentLineId ?? null,
|
|
2314
|
+
isVirtual: item.isVirtual ?? void 0,
|
|
2315
|
+
isUpsell: item.isUpsell ?? void 0,
|
|
2316
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
2317
|
+
customAttributes: item.customAttributes ?? [],
|
|
2318
|
+
sellingPlanId: item.sellingPlanId ?? null,
|
|
2319
|
+
sellingPlanName: item.sellingPlanName ?? null,
|
|
2320
|
+
nextBillingDate: item.nextBillingDate ?? null,
|
|
2321
|
+
billingPolicy: item.billingPolicy ?? null,
|
|
2322
|
+
subscriptionStatus: item.subscriptionStatus ?? null,
|
|
2323
|
+
subscriptionIsEditable: item.subscriptionIsEditable ?? null,
|
|
2324
|
+
requiredBillingCyclesLeft: item.requiredBillingCyclesLeft ?? null
|
|
2325
|
+
};
|
|
2326
|
+
}),
|
|
2327
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2328
|
+
discounts: (order.discounts ?? []).map((d3) => {
|
|
2329
|
+
var _a, _b, _c;
|
|
2330
|
+
return {
|
|
2331
|
+
id: d3.id,
|
|
2332
|
+
title: d3.title ?? void 0,
|
|
2333
|
+
type: ((_a = d3.value) == null ? void 0 : _a.type) === "fixed-amount" ? "fixed_amount" : "percentage",
|
|
2334
|
+
value: ((_b = d3.value) == null ? void 0 : _b.amount) ?? ((_c = d3.value) == null ? void 0 : _c.percentage) ?? 0
|
|
2335
|
+
};
|
|
2336
|
+
})
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
function toIsoOrNull(value) {
|
|
2340
|
+
const date = new Date(value);
|
|
2341
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
2342
|
+
}
|
|
2343
|
+
function buildMatcher(params) {
|
|
2344
|
+
const { cycle, date } = params;
|
|
2345
|
+
let iso;
|
|
2346
|
+
if (date != null) {
|
|
2347
|
+
const parsed = toIsoOrNull(date);
|
|
2348
|
+
if (parsed == null) return { ok: false, error: "date must be a valid date" };
|
|
2349
|
+
iso = parsed;
|
|
2350
|
+
}
|
|
2351
|
+
if (cycle != null && iso != null) {
|
|
2352
|
+
return { ok: true, matcher: { type: "CYCLE_AND_DATE", input: { cycle, date: iso } } };
|
|
2353
|
+
}
|
|
2354
|
+
if (cycle != null) return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
|
|
2355
|
+
if (iso != null) return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
|
|
2356
|
+
return { ok: false, error: "Either cycle or date is required" };
|
|
2357
|
+
}
|
|
2358
|
+
function buildSkipItemMatcher(params) {
|
|
2359
|
+
const { cycle, date } = params;
|
|
2360
|
+
if (cycle != null) {
|
|
2361
|
+
return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
|
|
2362
|
+
}
|
|
2363
|
+
if (date != null) {
|
|
2364
|
+
const iso = toIsoOrNull(date);
|
|
2365
|
+
if (iso == null) return { ok: false, error: "date must be a valid date" };
|
|
2366
|
+
return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
|
|
2367
|
+
}
|
|
2368
|
+
return { ok: false, error: "Either cycle or date is required" };
|
|
2369
|
+
}
|
|
2370
|
+
const DELIVERY_PRICE = 5;
|
|
2371
|
+
const DELIVERY_OFFSET_DAYS = 2;
|
|
2372
|
+
function toPrice(amount) {
|
|
2373
|
+
const n = Number.parseFloat(amount ?? "0");
|
|
2374
|
+
return Number.isFinite(n) ? n : 0;
|
|
2375
|
+
}
|
|
2376
|
+
function addInterval(date, interval, units) {
|
|
2377
|
+
const d3 = new Date(date);
|
|
2378
|
+
switch (interval) {
|
|
2379
|
+
case "DAY":
|
|
2380
|
+
d3.setDate(d3.getDate() + units);
|
|
2381
|
+
break;
|
|
2382
|
+
case "WEEK":
|
|
2383
|
+
d3.setDate(d3.getDate() + units * 7);
|
|
2384
|
+
break;
|
|
2385
|
+
case "YEAR":
|
|
2386
|
+
d3.setFullYear(d3.getFullYear() + units);
|
|
2387
|
+
break;
|
|
2388
|
+
default:
|
|
2389
|
+
d3.setMonth(d3.getMonth() + units);
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
return d3;
|
|
2393
|
+
}
|
|
2394
|
+
function mapItem(sub, item, cycle) {
|
|
2395
|
+
var _a, _b;
|
|
2396
|
+
const price = toPrice(((_a = item.currentPrice) == null ? void 0 : _a.amount) ?? item.price.amount);
|
|
2397
|
+
return {
|
|
2398
|
+
id: `${sub.id}_cycle_${cycle}_${item.id}`,
|
|
2399
|
+
title: item.title ?? `Product ${item.variantId}`,
|
|
2400
|
+
subtitle: item.variantTitle ?? null,
|
|
2401
|
+
imageUrl: (_b = item.variantImage) == null ? void 0 : _b.url,
|
|
2402
|
+
variantId: item.variantId,
|
|
2403
|
+
quantity: item.quantity,
|
|
2404
|
+
price,
|
|
2405
|
+
totalPrice: price * item.quantity,
|
|
2406
|
+
skipped: false,
|
|
2407
|
+
variantImage: item.variantImage ?? null,
|
|
2408
|
+
variantAvailableForSale: item.variantAvailableForSale ?? true,
|
|
2409
|
+
subscriptionId: sub.id,
|
|
2410
|
+
subscriptionLineId: item.id,
|
|
2411
|
+
productId: item.productId,
|
|
2412
|
+
parentProductId: item.parentProductId ?? null,
|
|
2413
|
+
parentLineId: item.parentLineId ?? null,
|
|
2414
|
+
isVirtual: item.isVirtual ?? false,
|
|
2415
|
+
isUpsell: item.isUpsell ?? false,
|
|
2416
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
2417
|
+
customAttributes: item.customAttributes ?? []
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function projectUpcomingOrders(sub, count) {
|
|
2421
|
+
var _a, _b;
|
|
2422
|
+
if (sub.status !== "active") return [];
|
|
2423
|
+
const billing = sub.billingPolicy ?? ((_a = sub.items[0]) == null ? void 0 : _a.billingPolicy) ?? { interval: "MONTH", intervalCount: 1 };
|
|
2424
|
+
const baseStr = sub.nextBillingDate ?? (sub.nextDeliveryDate ? sub.nextDeliveryDate.toISOString() : null);
|
|
2425
|
+
const base = baseStr ? new Date(baseStr) : /* @__PURE__ */ new Date();
|
|
2426
|
+
const currencyCode = ((_b = sub.items[0]) == null ? void 0 : _b.price.currencyCode) ?? "USD";
|
|
2427
|
+
const orders = [];
|
|
2428
|
+
for (let cycle = 0; cycle < count; cycle++) {
|
|
2429
|
+
const billingDate = addInterval(
|
|
2430
|
+
base,
|
|
2431
|
+
billing.interval,
|
|
2432
|
+
cycle * billing.intervalCount
|
|
2433
|
+
);
|
|
2434
|
+
const deliveryDate = addInterval(billingDate, "DAY", DELIVERY_OFFSET_DAYS);
|
|
2435
|
+
const items = sub.items.map((item) => mapItem(sub, item, cycle));
|
|
2436
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2437
|
+
orders.push({
|
|
2438
|
+
id: `${sub.id}_cycle_${cycle}`,
|
|
2439
|
+
customerId: sub.customerId,
|
|
2440
|
+
subscriptionId: sub.id,
|
|
2441
|
+
date: billingDate.toISOString(),
|
|
2442
|
+
skipped: false,
|
|
2443
|
+
currencyCode,
|
|
2444
|
+
subtotal,
|
|
2445
|
+
deliveryPrice: DELIVERY_PRICE,
|
|
2446
|
+
cycleIndex: cycle,
|
|
2447
|
+
deliveryDate: deliveryDate.toISOString(),
|
|
2448
|
+
status: "scheduled",
|
|
2449
|
+
items,
|
|
2450
|
+
discounts: sub.discounts.map((d3) => ({
|
|
2451
|
+
id: d3.id,
|
|
2452
|
+
title: d3.code,
|
|
2453
|
+
type: d3.type,
|
|
2454
|
+
value: d3.value
|
|
2455
|
+
}))
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
return orders;
|
|
2459
|
+
}
|
|
2460
|
+
function applyAdjustments(orders, stored) {
|
|
2461
|
+
return orders.map((order) => {
|
|
2462
|
+
let next = order;
|
|
2463
|
+
for (const { adjustment, subscriptionId, cycle } of stored) {
|
|
2464
|
+
if (subscriptionId !== order.subscriptionId) continue;
|
|
2465
|
+
if (cycle !== order.cycleIndex) continue;
|
|
2466
|
+
const { action } = adjustment;
|
|
2467
|
+
if (action.type === "SKIP_ORDER") {
|
|
2468
|
+
next = { ...next, skipped: true, status: "skipped" };
|
|
2469
|
+
} else if (action.type === "CHANGE_DATE") {
|
|
2470
|
+
next = {
|
|
2471
|
+
...next,
|
|
2472
|
+
date: action.input.newDate,
|
|
2473
|
+
deliveryDate: action.input.newDate
|
|
2474
|
+
};
|
|
2475
|
+
} else if (action.type === "SKIP_ITEM") {
|
|
2476
|
+
const lineIds = new Set(action.input.lineIds);
|
|
2477
|
+
const items = next.items.map(
|
|
2478
|
+
(item) => lineIds.has(item.subscriptionLineId) ? { ...item, skipped: true, totalPrice: 0 } : item
|
|
2479
|
+
);
|
|
2480
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2481
|
+
next = { ...next, items, subtotal };
|
|
2482
|
+
if (items.length > 0 && items.every((i2) => i2.skipped)) {
|
|
2483
|
+
next = { ...next, skipped: true, status: "skipped", deliveryPrice: 0 };
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return next;
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
function createMockSchedulesAdapter(subscriptions = createMockSubscriptionAdapter()) {
|
|
2491
|
+
const RESOLVE_DEPTH = 12;
|
|
2492
|
+
let stored = [];
|
|
2493
|
+
let idSeq = 0;
|
|
2494
|
+
async function subscriptionsToProject(subscriptionId) {
|
|
2495
|
+
if (subscriptionId) {
|
|
2496
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2497
|
+
return res._tag === "Success" && res.data ? [res.data] : [];
|
|
2498
|
+
}
|
|
2499
|
+
const all = [];
|
|
2500
|
+
let pagination;
|
|
2501
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2502
|
+
const res = await subscriptions.search(pagination ? { pagination } : {});
|
|
2503
|
+
if (res._tag !== "Success") break;
|
|
2504
|
+
all.push(...res.data.items);
|
|
2505
|
+
const { hasNextPage, endCursor } = res.data.pageInfo;
|
|
2506
|
+
if (!hasNextPage || !endCursor) break;
|
|
2507
|
+
pagination = { after: endCursor };
|
|
2508
|
+
}
|
|
2509
|
+
return all;
|
|
2510
|
+
}
|
|
2511
|
+
function resolveCycle(orders, opts) {
|
|
2512
|
+
if (opts.cycle != null) return opts.cycle;
|
|
2513
|
+
if (opts.date) {
|
|
2514
|
+
const iso = toIsoOrNull(opts.date);
|
|
2515
|
+
const target = iso == null ? void 0 : iso.split("T")[0];
|
|
2516
|
+
const match = target ? orders.find(
|
|
2517
|
+
(o2) => o2.deliveryDate.split("T")[0] === target || o2.date.split("T")[0] === target
|
|
2518
|
+
) : void 0;
|
|
2519
|
+
return match ? match.cycleIndex : null;
|
|
2520
|
+
}
|
|
2521
|
+
const firstScheduled = orders.find((o2) => o2.status === "scheduled");
|
|
2522
|
+
return (firstScheduled == null ? void 0 : firstScheduled.cycleIndex) ?? null;
|
|
2523
|
+
}
|
|
2524
|
+
async function recordAdjustment(subscriptionId, opts, buildAction, matcherOverride) {
|
|
2525
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2526
|
+
const sub = res._tag === "Success" ? res.data : null;
|
|
2527
|
+
if (!sub) return { _tag: "Failure", error: "No scheduled order found" };
|
|
2528
|
+
const orders = applyAdjustments(
|
|
2529
|
+
projectUpcomingOrders(sub, RESOLVE_DEPTH),
|
|
2530
|
+
stored
|
|
2531
|
+
);
|
|
2532
|
+
const cycle = resolveCycle(orders, opts);
|
|
2533
|
+
if (cycle == null) {
|
|
2534
|
+
return { _tag: "Failure", error: "No scheduled order found" };
|
|
2535
|
+
}
|
|
2536
|
+
const built = buildMatcher({ cycle, date: opts.date });
|
|
2537
|
+
const matcher = matcherOverride ?? (built.ok ? built.matcher : { type: "CYCLE", input: { cycle } });
|
|
2538
|
+
stored = [
|
|
2539
|
+
...stored,
|
|
2540
|
+
{
|
|
2541
|
+
adjustment: { id: `adj-${idSeq++}`, matcher, action: buildAction() },
|
|
2542
|
+
subscriptionId,
|
|
2543
|
+
cycle
|
|
2544
|
+
}
|
|
2545
|
+
];
|
|
2546
|
+
return { _tag: "Success", data: void 0 };
|
|
2547
|
+
}
|
|
2548
|
+
return {
|
|
2549
|
+
async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
|
|
2550
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2551
|
+
const subs = await subscriptionsToProject(subscriptionId);
|
|
2552
|
+
const skipCount = new Set(
|
|
2553
|
+
stored.filter(
|
|
2554
|
+
(s) => s.adjustment.action.type === "SKIP_ORDER" || s.adjustment.action.type === "SKIP_ITEM"
|
|
2555
|
+
).map((s) => `${s.subscriptionId}:${s.cycle}`)
|
|
2556
|
+
).size;
|
|
2557
|
+
const depth = excludeSkipped ? count + skipCount : count;
|
|
2558
|
+
const orders = subs.flatMap(
|
|
2559
|
+
(sub) => applyAdjustments(projectUpcomingOrders(sub, depth), stored)
|
|
2560
|
+
).filter((o2) => excludeSkipped ? !o2.skipped : true).map(
|
|
2561
|
+
(o2) => (
|
|
2562
|
+
// Mirror backend removeSkippedItems: the billable view also strips
|
|
2563
|
+
// skipped lines from surviving (partially-skipped) orders. No
|
|
2564
|
+
// recompute needed — skipped lines already carry totalPrice 0 (R4).
|
|
2565
|
+
excludeSkipped && o2.items.some((i2) => i2.skipped) ? { ...o2, items: o2.items.filter((i2) => !i2.skipped) } : o2
|
|
2566
|
+
)
|
|
2567
|
+
).sort((a8, b2) => a8.date.localeCompare(b2.date));
|
|
2568
|
+
return { _tag: "Success", data: orders.slice(0, count) };
|
|
2569
|
+
},
|
|
2570
|
+
async getAdjustments() {
|
|
2571
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2572
|
+
return { _tag: "Success", data: stored.map((s) => s.adjustment) };
|
|
2573
|
+
},
|
|
2574
|
+
async skipOrder({ subscriptionId, cycle, date }) {
|
|
2575
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2576
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2577
|
+
type: "SKIP_ORDER",
|
|
2578
|
+
input: { reason: "" }
|
|
2579
|
+
}));
|
|
2580
|
+
},
|
|
2581
|
+
async skipItem({ subscriptionId, lineId, cycle, date, reason }) {
|
|
2582
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2583
|
+
const built = buildSkipItemMatcher({ cycle, date });
|
|
2584
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2585
|
+
return recordAdjustment(
|
|
2586
|
+
subscriptionId,
|
|
2587
|
+
{ cycle, date },
|
|
2588
|
+
() => ({
|
|
2589
|
+
type: "SKIP_ITEM",
|
|
2590
|
+
input: { lineIds: [lineId], reason: reason ?? "" }
|
|
2591
|
+
}),
|
|
2592
|
+
built.matcher
|
|
2593
|
+
);
|
|
2594
|
+
},
|
|
2595
|
+
async changeDateAdjustment({ subscriptionId, newDate, cycle, currentDate }) {
|
|
2596
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2597
|
+
const newIso = toIsoOrNull(newDate);
|
|
2598
|
+
if (newIso == null) {
|
|
2599
|
+
return { _tag: "Failure", error: "newDate must be a valid date" };
|
|
2600
|
+
}
|
|
2601
|
+
return recordAdjustment(
|
|
2602
|
+
subscriptionId,
|
|
2603
|
+
{ cycle, date: currentDate },
|
|
2604
|
+
() => ({
|
|
2605
|
+
type: "CHANGE_DATE",
|
|
2606
|
+
input: { newDate: newIso }
|
|
2607
|
+
})
|
|
2608
|
+
);
|
|
2609
|
+
},
|
|
2610
|
+
async updateShippingAdjustment({ subscriptionId, address, cycle, date }) {
|
|
2611
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2612
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2613
|
+
type: "UPDATE_SHIPPING",
|
|
2614
|
+
input: { address }
|
|
2615
|
+
}));
|
|
2616
|
+
},
|
|
2617
|
+
async updatePaymentMethodAdjustment({
|
|
2618
|
+
subscriptionId,
|
|
2619
|
+
paymentMethodId,
|
|
2620
|
+
cycle,
|
|
2621
|
+
date
|
|
2622
|
+
}) {
|
|
2623
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2624
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2625
|
+
type: "UPDATE_PAYMENT_METHOD",
|
|
2626
|
+
input: { paymentMethodId }
|
|
2627
|
+
}));
|
|
2628
|
+
},
|
|
2629
|
+
async updateProductsAdjustment({ subscriptionId, lines, cycle, date }) {
|
|
2630
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2631
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2632
|
+
type: "UPDATE_PRODUCTS",
|
|
2633
|
+
input: { lines }
|
|
2634
|
+
}));
|
|
2635
|
+
},
|
|
2636
|
+
async applyDiscountAdjustment({ subscriptionId, discountCode, cycle }) {
|
|
2637
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2638
|
+
return recordAdjustment(subscriptionId, { cycle }, () => ({
|
|
2639
|
+
type: "APPLY_DISCOUNT",
|
|
2640
|
+
input: { discountSource: "code", discountCode }
|
|
2641
|
+
}));
|
|
2642
|
+
},
|
|
2643
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2644
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2645
|
+
stored = stored.filter((s) => s.adjustment.id !== adjustmentId);
|
|
2646
|
+
return { _tag: "Success", data: void 0 };
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function createApiSchedulesAdapter(fetcher) {
|
|
2651
|
+
async function postAdjustment(matcher, action) {
|
|
2652
|
+
try {
|
|
2653
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2654
|
+
method: "POST",
|
|
2655
|
+
headers: { "Content-Type": "application/json" },
|
|
2656
|
+
body: JSON.stringify({ matcher, action })
|
|
2657
|
+
});
|
|
2658
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2659
|
+
return { _tag: "Success", data: void 0 };
|
|
2660
|
+
} catch (e2) {
|
|
2661
|
+
return { _tag: "Failure", error: e2 };
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return {
|
|
2665
|
+
async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
|
|
2666
|
+
try {
|
|
2667
|
+
const params = new URLSearchParams({ count: String(count) });
|
|
2668
|
+
params.set("excludeSkipped", String(excludeSkipped));
|
|
2669
|
+
if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
|
|
2670
|
+
const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
|
|
2671
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2672
|
+
const data = await res.json();
|
|
2673
|
+
return { _tag: "Success", data: (data.orders ?? []).map(mapScheduleOrder) };
|
|
2674
|
+
} catch (e2) {
|
|
2675
|
+
return { _tag: "Failure", error: e2 };
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2678
|
+
async getAdjustments() {
|
|
2679
|
+
try {
|
|
2680
|
+
const all = [];
|
|
2681
|
+
let after;
|
|
2682
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2683
|
+
const params = new URLSearchParams({ limit: "100" });
|
|
2684
|
+
if (after) params.set("after", after);
|
|
2685
|
+
const res = await fetcher(
|
|
2686
|
+
`/customer/v1/schedules/adjustments?${params.toString()}`
|
|
2687
|
+
);
|
|
2688
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2689
|
+
const body = await res.json();
|
|
2690
|
+
all.push(
|
|
2691
|
+
...body.data.map((a8) => ({
|
|
2692
|
+
id: a8.id,
|
|
2693
|
+
matcher: a8.matcher,
|
|
2694
|
+
action: a8.action
|
|
2695
|
+
}))
|
|
2696
|
+
);
|
|
2697
|
+
if (!body.hasNextPage) return { _tag: "Success", data: all };
|
|
2698
|
+
if (!body.endCursor || body.endCursor === after) {
|
|
2699
|
+
return {
|
|
2700
|
+
_tag: "Failure",
|
|
2701
|
+
error: "Adjustments pagination cursor did not advance"
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
after = body.endCursor;
|
|
2705
|
+
}
|
|
2706
|
+
return {
|
|
2707
|
+
_tag: "Failure",
|
|
2708
|
+
error: "Adjustments pagination exceeded 50 pages"
|
|
2709
|
+
};
|
|
2710
|
+
} catch (e2) {
|
|
2711
|
+
return { _tag: "Failure", error: e2 };
|
|
2712
|
+
}
|
|
2713
|
+
},
|
|
2714
|
+
async skipOrder({ cycle, date }) {
|
|
2715
|
+
const built = buildMatcher({ cycle, date });
|
|
2716
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2717
|
+
return postAdjustment(built.matcher, {
|
|
2718
|
+
type: "SKIP_ORDER",
|
|
2719
|
+
input: { reason: "" }
|
|
2720
|
+
});
|
|
2721
|
+
},
|
|
2722
|
+
async skipItem({ lineId, cycle, date, reason }) {
|
|
2723
|
+
const built = buildSkipItemMatcher({ cycle, date });
|
|
2724
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2725
|
+
return postAdjustment(built.matcher, {
|
|
2726
|
+
type: "SKIP_ITEM",
|
|
2727
|
+
input: { lineIds: [lineId], reason: reason ?? "" }
|
|
2728
|
+
});
|
|
2729
|
+
},
|
|
2730
|
+
async changeDateAdjustment({ newDate, cycle }) {
|
|
2731
|
+
if (cycle == null) {
|
|
2732
|
+
return {
|
|
2733
|
+
_tag: "Failure",
|
|
2734
|
+
error: "cycle is required for changeDateAdjustment"
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
const newIso = toIsoOrNull(newDate);
|
|
2738
|
+
if (newIso == null) {
|
|
2739
|
+
return { _tag: "Failure", error: "newDate must be a valid date" };
|
|
2740
|
+
}
|
|
2741
|
+
return postAdjustment(
|
|
2742
|
+
{ type: "CYCLE", input: { cycle } },
|
|
2743
|
+
{ type: "CHANGE_DATE", input: { newDate: newIso } }
|
|
2744
|
+
);
|
|
2745
|
+
},
|
|
2746
|
+
async updateShippingAdjustment({ address, cycle, date }) {
|
|
2747
|
+
const built = buildMatcher({ cycle, date });
|
|
2748
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2749
|
+
return postAdjustment(built.matcher, {
|
|
2750
|
+
type: "UPDATE_SHIPPING",
|
|
2751
|
+
input: { address }
|
|
2752
|
+
});
|
|
2753
|
+
},
|
|
2754
|
+
async updatePaymentMethodAdjustment({ paymentMethodId, cycle, date }) {
|
|
2755
|
+
const built = buildMatcher({ cycle, date });
|
|
2756
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2757
|
+
return postAdjustment(built.matcher, {
|
|
2758
|
+
type: "UPDATE_PAYMENT_METHOD",
|
|
2759
|
+
input: { paymentMethodId }
|
|
2760
|
+
});
|
|
2761
|
+
},
|
|
2762
|
+
async updateProductsAdjustment({ lines, cycle, date }) {
|
|
2763
|
+
const built = buildMatcher({ cycle, date });
|
|
2764
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2765
|
+
return postAdjustment(built.matcher, {
|
|
2766
|
+
type: "UPDATE_PRODUCTS",
|
|
2767
|
+
input: { lines }
|
|
2768
|
+
});
|
|
2769
|
+
},
|
|
2770
|
+
async applyDiscountAdjustment({ discountCode, cycle }) {
|
|
2771
|
+
return postAdjustment(
|
|
2772
|
+
{ type: "CYCLE", input: { cycle } },
|
|
2773
|
+
{
|
|
2774
|
+
type: "APPLY_DISCOUNT",
|
|
2775
|
+
input: { discountSource: "code", discountCode }
|
|
2776
|
+
}
|
|
2777
|
+
);
|
|
2778
|
+
},
|
|
2779
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2780
|
+
try {
|
|
2781
|
+
const res = await fetcher(
|
|
2782
|
+
`/customer/v1/schedules/adjustments/${adjustmentId}`,
|
|
2783
|
+
{ method: "DELETE" }
|
|
2784
|
+
);
|
|
2785
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2786
|
+
return { _tag: "Success", data: void 0 };
|
|
2787
|
+
} catch (e2) {
|
|
2788
|
+
return { _tag: "Failure", error: e2 };
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
const SchedulesServiceContext = createContext("services/schedules");
|
|
2794
|
+
function isSameCalendarDay(a8, b2) {
|
|
2795
|
+
const da = new Date(a8);
|
|
2796
|
+
const db = new Date(b2);
|
|
2797
|
+
if (Number.isNaN(da.getTime()) || Number.isNaN(db.getTime())) return false;
|
|
2798
|
+
return da.toISOString().slice(0, 10) === db.toISOString().slice(0, 10);
|
|
2799
|
+
}
|
|
2800
|
+
function matcherArms(matcher, target) {
|
|
2801
|
+
const cycleArm = (matcher.type === "CYCLE" || matcher.type === "CYCLE_AND_DATE") && matcher.input.cycle === target.cycle;
|
|
2802
|
+
const dateArm = (matcher.type === "DATE" || matcher.type === "CYCLE_AND_DATE") && isSameCalendarDay(matcher.input.date, target.date);
|
|
2803
|
+
return { cycleArm, dateArm };
|
|
2804
|
+
}
|
|
2805
|
+
function matcherAffects(matcher, target) {
|
|
2806
|
+
const { cycleArm, dateArm } = matcherArms(matcher, target);
|
|
2807
|
+
return cycleArm || dateArm;
|
|
2808
|
+
}
|
|
2809
|
+
function matcherExactlyTargets(matcher, target) {
|
|
2810
|
+
const { cycleArm, dateArm } = matcherArms(matcher, target);
|
|
2811
|
+
if (matcher.type === "CYCLE") return cycleArm;
|
|
2812
|
+
if (matcher.type === "DATE") return dateArm;
|
|
2813
|
+
return cycleArm && dateArm;
|
|
2814
|
+
}
|
|
2815
|
+
function createSchedulesService(adapter, opts) {
|
|
2816
|
+
const current = d(null);
|
|
2817
|
+
const currentLoading = d(false);
|
|
2818
|
+
const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
|
|
2819
|
+
const schedulesChangedAt = d(0);
|
|
2820
|
+
let reloadToken = 0;
|
|
2821
|
+
let currentSubscriptionId;
|
|
2822
|
+
async function getUpcomingOrder() {
|
|
2823
|
+
const result = await adapter.getUpcomingOrders({ count: 1 });
|
|
2824
|
+
if (result._tag === "Failure") return result;
|
|
2825
|
+
return { _tag: "Success", data: result.data[0] ?? null };
|
|
2826
|
+
}
|
|
2827
|
+
async function getUpcomingOrders(count, opts2) {
|
|
2828
|
+
return adapter.getUpcomingOrders({ count, ...opts2 });
|
|
2829
|
+
}
|
|
2830
|
+
async function reloadCurrent(subscriptionId) {
|
|
2831
|
+
var _a;
|
|
2832
|
+
const scopeChanged = currentSubscriptionId !== subscriptionId;
|
|
2833
|
+
currentSubscriptionId = subscriptionId;
|
|
2834
|
+
const token = ++reloadToken;
|
|
2835
|
+
currentLoading.value = true;
|
|
2836
|
+
const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
|
|
2837
|
+
if (scopeChanged) current.value = null;
|
|
2838
|
+
const result = await adapter.getUpcomingOrders({
|
|
2839
|
+
count,
|
|
2840
|
+
subscriptionId
|
|
2841
|
+
});
|
|
2842
|
+
if (token !== reloadToken) return;
|
|
2843
|
+
currentLoading.value = false;
|
|
2844
|
+
if (result._tag === "Success") {
|
|
2845
|
+
current.value = result.data;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
async function notifyChanged(subscriptionId) {
|
|
2849
|
+
if (subscriptionId) await reloadCurrent(subscriptionId);
|
|
2850
|
+
schedulesChangedAt.value++;
|
|
2851
|
+
}
|
|
2852
|
+
return {
|
|
2853
|
+
current,
|
|
2854
|
+
currentLoading,
|
|
2855
|
+
hasPendingBilling,
|
|
2856
|
+
schedulesChangedAt,
|
|
2857
|
+
getUpcomingOrder,
|
|
2858
|
+
getUpcomingOrders,
|
|
2859
|
+
async getAdjustments() {
|
|
2860
|
+
return adapter.getAdjustments();
|
|
2861
|
+
},
|
|
2862
|
+
async skipOrder(params) {
|
|
2863
|
+
const result = await adapter.skipOrder(params);
|
|
2864
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2865
|
+
return result;
|
|
2866
|
+
},
|
|
2867
|
+
async skipItem(params) {
|
|
2868
|
+
const result = await adapter.skipItem(params);
|
|
2869
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2870
|
+
return result;
|
|
2871
|
+
},
|
|
2872
|
+
async unskipItem({ subscriptionId, lineId, cycle, date }) {
|
|
2873
|
+
const adjustments = await adapter.getAdjustments();
|
|
2874
|
+
if (adjustments._tag === "Failure") return adjustments;
|
|
2875
|
+
const covering = adjustments.data.filter(
|
|
2876
|
+
(a8) => a8.action.type === "SKIP_ITEM" && a8.action.input.lineIds.includes(lineId) && matcherAffects(a8.matcher, { cycle, date })
|
|
2877
|
+
);
|
|
2878
|
+
if (covering.length === 0) {
|
|
2879
|
+
return { _tag: "Failure", error: "No skip found for this item" };
|
|
2880
|
+
}
|
|
2881
|
+
if (covering.some((a8) => a8.action.input.lineIds.length > 1)) {
|
|
2882
|
+
return {
|
|
2883
|
+
_tag: "Failure",
|
|
2884
|
+
error: "This item was skipped together with other items and can't be unskipped individually"
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
if (covering.some((a8) => !matcherExactlyTargets(a8.matcher, { cycle, date }))) {
|
|
2888
|
+
return {
|
|
2889
|
+
_tag: "Failure",
|
|
2890
|
+
error: "This skip also affects another order and can't be removed from here"
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
let deleted = 0;
|
|
2894
|
+
for (const a8 of covering) {
|
|
2895
|
+
const result = await adapter.removeAdjustment({ adjustmentId: a8.id });
|
|
2896
|
+
if (result._tag === "Failure") {
|
|
2897
|
+
if (deleted > 0) await notifyChanged(subscriptionId);
|
|
2898
|
+
return result;
|
|
2899
|
+
}
|
|
2900
|
+
deleted++;
|
|
2901
|
+
}
|
|
2902
|
+
await notifyChanged(subscriptionId);
|
|
2903
|
+
return { _tag: "Success", data: void 0 };
|
|
2904
|
+
},
|
|
2905
|
+
async changeDateAdjustment(params) {
|
|
2906
|
+
const result = await adapter.changeDateAdjustment(params);
|
|
2907
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2908
|
+
return result;
|
|
2909
|
+
},
|
|
2910
|
+
async updateShippingAdjustment(params) {
|
|
2911
|
+
const result = await adapter.updateShippingAdjustment(params);
|
|
2912
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2913
|
+
return result;
|
|
2914
|
+
},
|
|
2915
|
+
async updatePaymentMethodAdjustment(params) {
|
|
2916
|
+
const result = await adapter.updatePaymentMethodAdjustment(params);
|
|
2917
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2918
|
+
return result;
|
|
2919
|
+
},
|
|
2920
|
+
async updateProductsAdjustment(params) {
|
|
2921
|
+
const result = await adapter.updateProductsAdjustment(params);
|
|
2922
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2923
|
+
return result;
|
|
2924
|
+
},
|
|
2925
|
+
async applyDiscountAdjustment(params) {
|
|
2926
|
+
const result = await adapter.applyDiscountAdjustment(params);
|
|
2927
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2928
|
+
return result;
|
|
2929
|
+
},
|
|
2930
|
+
async removeAdjustment(params) {
|
|
2931
|
+
var _a, _b, _c;
|
|
2932
|
+
const result = await adapter.removeAdjustment(params);
|
|
2933
|
+
if (result._tag === "Success") {
|
|
2934
|
+
const order = (_a = current.value) == null ? void 0 : _a[0];
|
|
2935
|
+
const subscriptionId = currentSubscriptionId ?? (order == null ? void 0 : order.subscriptionId) ?? ((_c = (_b = order == null ? void 0 : order.items) == null ? void 0 : _b[0]) == null ? void 0 : _c.subscriptionId);
|
|
2936
|
+
await notifyChanged(subscriptionId);
|
|
2937
|
+
}
|
|
2938
|
+
return result;
|
|
2939
|
+
},
|
|
2940
|
+
reloadCurrent
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
const SettingsServiceContext = createContext("services/settings");
|
|
2944
|
+
function createSettingsService(initial = {}) {
|
|
2945
|
+
const current = d(initial);
|
|
2946
|
+
return {
|
|
2947
|
+
current,
|
|
2948
|
+
get(key) {
|
|
2949
|
+
return this.current.value[key];
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
function createShopService(options) {
|
|
2954
|
+
const contactEmail = d(options.getContactEmail());
|
|
2955
|
+
const service = { contactEmail };
|
|
2956
|
+
return service;
|
|
2957
|
+
}
|
|
2958
|
+
const ShopServiceContext = createContext("services/shop");
|
|
2959
|
+
const SubscriptionServiceContext = createContext("services/subscription");
|
|
2960
|
+
function getCurrentSubscriptionId() {
|
|
2961
|
+
if (typeof window === "undefined") {
|
|
2962
|
+
return null;
|
|
2963
|
+
}
|
|
2964
|
+
const pathname = window.location.pathname;
|
|
2965
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
2966
|
+
const subscriptionIndex = segments.findIndex(
|
|
2967
|
+
(segment) => segment === "subscriptions" || segment === "subscription"
|
|
2968
|
+
);
|
|
2969
|
+
if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
|
|
2970
|
+
return segments[subscriptionIndex + 1];
|
|
2971
|
+
}
|
|
2972
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2973
|
+
const lastSegment = segments[segments.length - 1];
|
|
2974
|
+
if (lastSegment && uuidPattern.test(lastSegment)) {
|
|
2975
|
+
return lastSegment;
|
|
2976
|
+
}
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
function createSubscriptionService(adapter) {
|
|
2980
|
+
const current = d(null);
|
|
2981
|
+
const hasPendingBilling = w(
|
|
2982
|
+
() => {
|
|
2983
|
+
var _a, _b, _c, _d;
|
|
2984
|
+
return ((_b = (_a = current.value) == null ? void 0 : _a.lastBillingAttempt) == null ? void 0 : _b.ready) === false || ((_d = (_c = current.value) == null ? void 0 : _c.renewal) == null ? void 0 : _d.isInActiveProcessState) === true;
|
|
2985
|
+
}
|
|
2986
|
+
);
|
|
2987
|
+
const discountsChangedAt = d(0);
|
|
2988
|
+
const itemsChangedAt = d(0);
|
|
2989
|
+
const lastOrder = d(null);
|
|
2990
|
+
async function refreshAfterDiscountChange() {
|
|
2991
|
+
await reload();
|
|
2992
|
+
discountsChangedAt.value = discountsChangedAt.value + 1;
|
|
2993
|
+
}
|
|
2994
|
+
async function refreshAfterItemChange() {
|
|
2995
|
+
await reload();
|
|
2996
|
+
itemsChangedAt.value = itemsChangedAt.value + 1;
|
|
2997
|
+
}
|
|
2998
|
+
function subscriptionIdFromCurrent() {
|
|
2999
|
+
var _a;
|
|
3000
|
+
return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
|
|
3001
|
+
}
|
|
3002
|
+
async function reload() {
|
|
3003
|
+
var _a, _b;
|
|
3004
|
+
const id = (_a = current.value) == null ? void 0 : _a.id;
|
|
3005
|
+
if (!id) return;
|
|
3006
|
+
const [subResult, lastOrderResult] = await Promise.all([
|
|
3007
|
+
adapter.getById(id),
|
|
3008
|
+
adapter.getLastOrder({ subscriptionId: id })
|
|
3009
|
+
]);
|
|
3010
|
+
if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
|
|
3011
|
+
if (subResult._tag === "Success") current.value = subResult.data;
|
|
3012
|
+
if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
|
|
3013
|
+
}
|
|
3014
|
+
const service = {
|
|
3015
|
+
current,
|
|
3016
|
+
hasPendingBilling,
|
|
3017
|
+
discountsChangedAt,
|
|
3018
|
+
itemsChangedAt,
|
|
3019
|
+
lastOrder,
|
|
3020
|
+
search: adapter.search,
|
|
3021
|
+
// Prefer the adapter's rich cross-subscription list (legacy endpoint, active-first).
|
|
3022
|
+
// Fall back to a single `search` page for adapters that don't implement it
|
|
3023
|
+
// (e.g. the editor mock) so the cross-subscription views still render.
|
|
3024
|
+
listAll: adapter.listAll ? adapter.listAll.bind(adapter) : async () => {
|
|
3025
|
+
const result = await adapter.search();
|
|
3026
|
+
if (result._tag !== "Success") return result;
|
|
3027
|
+
return { _tag: "Success", data: result.data.items };
|
|
3028
|
+
},
|
|
3029
|
+
getById: adapter.getById,
|
|
3030
|
+
getCurrentSubscriptionId,
|
|
3031
|
+
addItem: async (subscriptionId, item) => {
|
|
3032
|
+
const result = await adapter.addItem({ subscriptionId, item });
|
|
3033
|
+
if (result._tag === "Success") {
|
|
3034
|
+
await refreshAfterItemChange();
|
|
3035
|
+
}
|
|
3036
|
+
return result;
|
|
3037
|
+
},
|
|
3038
|
+
removeItem: async (subscriptionId, itemId) => {
|
|
3039
|
+
const result = await adapter.removeItem({ subscriptionId, itemId });
|
|
3040
|
+
if (result._tag === "Success") {
|
|
3041
|
+
await refreshAfterItemChange();
|
|
3042
|
+
}
|
|
3043
|
+
return result;
|
|
3044
|
+
},
|
|
3045
|
+
updateItem: async (subscriptionId, lineId, update) => {
|
|
3046
|
+
const result = await adapter.updateItem({ subscriptionId, lineId, update });
|
|
3047
|
+
if (result._tag === "Success") {
|
|
3048
|
+
await refreshAfterItemChange();
|
|
3049
|
+
}
|
|
3050
|
+
return result;
|
|
3051
|
+
},
|
|
3052
|
+
updateItemAttributes: async (subscriptionId, lineId, customAttributes) => {
|
|
3053
|
+
const result = await adapter.updateItemAttributes({ subscriptionId, lineId, customAttributes });
|
|
3054
|
+
if (result._tag === "Success") {
|
|
3055
|
+
await refreshAfterItemChange();
|
|
3056
|
+
}
|
|
3057
|
+
return result;
|
|
3058
|
+
},
|
|
3059
|
+
updateAttributes: async (subscriptionId, customAttributes) => {
|
|
3060
|
+
const result = await adapter.updateAttributes({ subscriptionId, customAttributes });
|
|
3061
|
+
if (result._tag === "Success") {
|
|
3062
|
+
await reload();
|
|
3063
|
+
}
|
|
3064
|
+
return result;
|
|
3065
|
+
},
|
|
3066
|
+
skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
|
|
3067
|
+
const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
|
|
3068
|
+
if (result._tag === "Success") {
|
|
3069
|
+
await refreshAfterItemChange();
|
|
3070
|
+
}
|
|
3071
|
+
return result;
|
|
3072
|
+
},
|
|
3073
|
+
updateBoxItems: async (subscriptionId, params) => {
|
|
3074
|
+
const result = await adapter.updateBoxItems({ subscriptionId, ...params });
|
|
3075
|
+
if (result._tag === "Success") {
|
|
3076
|
+
await refreshAfterItemChange();
|
|
3077
|
+
}
|
|
3078
|
+
return result;
|
|
3079
|
+
},
|
|
3080
|
+
addDiscount: async (code, subscriptionId) => {
|
|
3081
|
+
const id = subscriptionId ?? subscriptionIdFromCurrent();
|
|
3082
|
+
if (id == null) {
|
|
3083
|
+
return {
|
|
3084
|
+
_tag: "Failure",
|
|
3085
|
+
error: { kind: "SUBSCRIPTION_NOT_FOUND" }
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
const result = await adapter.addDiscount({ subscriptionId: id, code });
|
|
3089
|
+
if (result._tag === "Success") {
|
|
3090
|
+
await refreshAfterDiscountChange();
|
|
3091
|
+
}
|
|
3092
|
+
return result;
|
|
3093
|
+
},
|
|
3094
|
+
removeDiscount: async (discountId, subscriptionId) => {
|
|
3095
|
+
const id = subscriptionId ?? subscriptionIdFromCurrent();
|
|
3096
|
+
if (id == null) {
|
|
3097
|
+
return {
|
|
3098
|
+
_tag: "Failure",
|
|
3099
|
+
error: { kind: "SUBSCRIPTION_NOT_FOUND" }
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
const result = await adapter.removeDiscount({
|
|
3103
|
+
subscriptionId: id,
|
|
3104
|
+
discountId
|
|
3105
|
+
});
|
|
3106
|
+
if (result._tag === "Success") {
|
|
3107
|
+
await refreshAfterDiscountChange();
|
|
1193
3108
|
}
|
|
1194
|
-
const result = await adapter.removeDiscount({
|
|
1195
|
-
subscriptionId: id,
|
|
1196
|
-
discountId
|
|
1197
|
-
});
|
|
1198
|
-
if (result._tag === "Success") {
|
|
1199
|
-
await refreshAfterDiscountChange(id);
|
|
1200
|
-
}
|
|
1201
3109
|
return result;
|
|
1202
3110
|
},
|
|
1203
3111
|
reschedule: (subscriptionId, newDate) => adapter.reschedule({ subscriptionId, newDate }),
|
|
3112
|
+
updateCustomAttributes: (subscriptionId, customAttributes) => adapter.updateCustomAttributes({ subscriptionId, customAttributes }),
|
|
1204
3113
|
resume: (subscriptionId) => adapter.resume({ subscriptionId }),
|
|
1205
3114
|
pause: (subscriptionId) => adapter.pause({ subscriptionId }),
|
|
1206
3115
|
reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
|
|
1207
3116
|
cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
|
|
3117
|
+
renewNow: (params) => adapter.renewNow(params),
|
|
3118
|
+
changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
|
|
3119
|
+
reload,
|
|
1208
3120
|
getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
|
|
1209
3121
|
getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
itemsChangedAt: itemsChangedAtSignal
|
|
3122
|
+
getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
|
|
3123
|
+
addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
|
|
1213
3124
|
};
|
|
1214
3125
|
return service;
|
|
1215
3126
|
}
|
|
1216
|
-
const DISCOUNT_REJECTION_REASONS = [
|
|
1217
|
-
"INVALID_CODE",
|
|
1218
|
-
"INACTIVE",
|
|
1219
|
-
"NOT_APPLICABLE_TO_CUSTOMER",
|
|
1220
|
-
"ALREADY_ADDED",
|
|
1221
|
-
"ALREADY_USED",
|
|
1222
|
-
"NOT_APPLICABLE_TO_PRODUCTS",
|
|
1223
|
-
"NOT_APPLICABLE_TO_SUBSCRIPTION",
|
|
1224
|
-
"INVALID_COMBINATION",
|
|
1225
|
-
"QUANTITY_REQUIREMENT_NOT_MET"
|
|
1226
|
-
];
|
|
1227
3127
|
function getDefaultBlockForAction(actionType, actionConfig, blockId) {
|
|
1228
3128
|
const blockName = `action:${actionType}`;
|
|
1229
3129
|
const definedBlocks = getDefinedBlocks();
|
|
@@ -2200,7 +4100,10 @@ export {
|
|
|
2200
4100
|
P as PAGE_ROOT_SLOT,
|
|
2201
4101
|
ProductServiceContext,
|
|
2202
4102
|
RouterServiceContext,
|
|
4103
|
+
SchedulesServiceContext,
|
|
2203
4104
|
S as SelectedBlockContext,
|
|
4105
|
+
SettingsServiceContext,
|
|
4106
|
+
ShopServiceContext,
|
|
2204
4107
|
u as Signal,
|
|
2205
4108
|
StateContext,
|
|
2206
4109
|
SubscriptionServiceContext,
|
|
@@ -2219,6 +4122,12 @@ export {
|
|
|
2219
4122
|
compactItems,
|
|
2220
4123
|
I as compactLayoutDocument,
|
|
2221
4124
|
w as computed,
|
|
4125
|
+
createApiCustomerAdapter,
|
|
4126
|
+
createApiLoginAdapter,
|
|
4127
|
+
createApiOrdersAdapter,
|
|
4128
|
+
createApiProductAdapter,
|
|
4129
|
+
createApiSchedulesAdapter,
|
|
4130
|
+
createApiSubscriptionAdapter,
|
|
2222
4131
|
createBlockInstance,
|
|
2223
4132
|
J as createBlockInstanceFromJSON,
|
|
2224
4133
|
K as createBlockInstanceFromObject,
|
|
@@ -2226,15 +4135,22 @@ export {
|
|
|
2226
4135
|
createContext,
|
|
2227
4136
|
createCustomerService,
|
|
2228
4137
|
O as createDefaultLayoutDocument,
|
|
4138
|
+
createLoginService,
|
|
4139
|
+
createMockCustomerAdapter,
|
|
4140
|
+
createMockLoginAdapter,
|
|
2229
4141
|
createMockOrdersAdapter,
|
|
2230
4142
|
createMockProductAdapter,
|
|
2231
4143
|
createMockRouterService,
|
|
4144
|
+
createMockSchedulesAdapter,
|
|
2232
4145
|
createMockSubscriptionAdapter,
|
|
2233
4146
|
createMockWorkflowApiAdapter,
|
|
2234
4147
|
createOrdersService,
|
|
2235
4148
|
createProductService,
|
|
2236
4149
|
createRouterService,
|
|
4150
|
+
createSchedulesService,
|
|
2237
4151
|
Q as createSelectedBlock,
|
|
4152
|
+
createSettingsService,
|
|
4153
|
+
createShopService,
|
|
2238
4154
|
createState,
|
|
2239
4155
|
createSubscriptionService,
|
|
2240
4156
|
R as createThemeState,
|