@rebuy/rebuy 3.56.3 → 3.58.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/gwp/index.cjs +2 -2
- package/dist/gwp/index.mjs +2 -2
- package/dist/index.cjs +30 -13
- package/dist/index.mjs +30 -13
- package/dist/schema/offerProduct.d.ts +3 -3
- package/dist/schema/postPurchaseFollowups.d.ts +4 -0
- package/dist/schema/postPurchaseRecovery.d.ts +4 -0
- package/dist/schema/postPurchaseSts.d.ts +25 -1
- package/dist/server/classifyPostPurchaseOffer.d.ts +4 -1
- package/dist/server/index.cjs +65 -34
- package/dist/server/index.mjs +65 -34
- package/dist/server/postPurchaseModernPlans.d.ts +5 -0
- package/dist/server/postPurchasePlanCatalog.d.ts +4 -0
- package/dist/server/postPurchaseResponseDrift.d.ts +13 -0
- package/dist/server/postPurchaseSellingPlanRequirement.d.ts +14 -0
- package/dist/server/postPurchaseSts.d.ts +10 -2
- package/dist/server/shared.d.ts +10 -7
- package/package.json +1 -1
package/dist/gwp/index.cjs
CHANGED
|
@@ -37,8 +37,8 @@ var getValidGwpProductIds = (matchedRules = []) => new Set(
|
|
|
37
37
|
|
|
38
38
|
// src/gwp/hasUnsupportedRuleTypes.ts
|
|
39
39
|
var UNSUPPORTED_RULE_TYPES = ["url", "order_tag"];
|
|
40
|
-
var
|
|
41
|
-
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.
|
|
40
|
+
var UNSUPPORTED_RULE_TYPE_FRAGMENT = "geolocation";
|
|
41
|
+
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.includes(UNSUPPORTED_RULE_TYPE_FRAGMENT);
|
|
42
42
|
var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some((group) => (group.rules ?? []).some((item) => isUnsupportedType(item.type)));
|
|
43
43
|
var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
|
|
44
44
|
|
package/dist/gwp/index.mjs
CHANGED
|
@@ -5,8 +5,8 @@ var getValidGwpProductIds = (matchedRules = []) => new Set(
|
|
|
5
5
|
|
|
6
6
|
// src/gwp/hasUnsupportedRuleTypes.ts
|
|
7
7
|
var UNSUPPORTED_RULE_TYPES = ["url", "order_tag"];
|
|
8
|
-
var
|
|
9
|
-
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.
|
|
8
|
+
var UNSUPPORTED_RULE_TYPE_FRAGMENT = "geolocation";
|
|
9
|
+
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.includes(UNSUPPORTED_RULE_TYPE_FRAGMENT);
|
|
10
10
|
var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some((group) => (group.rules ?? []).some((item) => isUnsupportedType(item.type)));
|
|
11
11
|
var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
|
|
12
12
|
|
package/dist/index.cjs
CHANGED
|
@@ -1698,7 +1698,7 @@ var OfferProductDiscount = import_zod5.z.object({
|
|
|
1698
1698
|
type: import_zod5.z.string()
|
|
1699
1699
|
});
|
|
1700
1700
|
var OfferProductOption = import_zod5.z.object({
|
|
1701
|
-
id: import_zod5.z.number(),
|
|
1701
|
+
id: import_zod5.z.number().optional(),
|
|
1702
1702
|
name: import_zod5.z.string(),
|
|
1703
1703
|
position: import_zod5.z.number(),
|
|
1704
1704
|
values: import_zod5.z.array(import_zod5.z.string())
|
|
@@ -1710,7 +1710,7 @@ var OfferProductVariant = import_zod5.z.object({
|
|
|
1710
1710
|
inventoryManagement: import_zod5.z.string().nullable(),
|
|
1711
1711
|
inventoryPolicy: import_zod5.z.string(),
|
|
1712
1712
|
inventoryQuantity: import_zod5.z.number(),
|
|
1713
|
-
link: import_zod5.z.string(),
|
|
1713
|
+
link: import_zod5.z.string().optional(),
|
|
1714
1714
|
option1: import_zod5.z.string(),
|
|
1715
1715
|
option2: import_zod5.z.string().nullable(),
|
|
1716
1716
|
option3: import_zod5.z.string().nullable(),
|
|
@@ -1723,12 +1723,12 @@ var OfferProduct = import_zod5.z.object({
|
|
|
1723
1723
|
id: import_zod5.z.number(),
|
|
1724
1724
|
image: import_zod5.z.object({ src: import_zod5.z.string() }).nullish(),
|
|
1725
1725
|
/**
|
|
1726
|
-
* `variantIds` is
|
|
1726
|
+
* `variantIds` is nullish: the engine omits `variant_ids` (rather than sending `[]`) on an image tied to no
|
|
1727
1727
|
* variant — seen live on the `recommended` data source while `top_sellers` sends `[]` for the same image
|
|
1728
|
-
* (REB-24958). A missing list means "not variant-specific", exactly like `[]`, so the image
|
|
1729
|
-
* and the product is never dropped over it.
|
|
1728
|
+
* (REB-24958). A missing (or `null`) list means "not variant-specific", exactly like `[]`, so the image
|
|
1729
|
+
* keeps its `src` and the product is never dropped over it.
|
|
1730
1730
|
*/
|
|
1731
|
-
images: import_zod5.z.array(import_zod5.z.object({ src: import_zod5.z.string(), variantIds: import_zod5.z.array(import_zod5.z.number()).
|
|
1731
|
+
images: import_zod5.z.array(import_zod5.z.object({ src: import_zod5.z.string(), variantIds: import_zod5.z.array(import_zod5.z.number()).nullish() })).nullish(),
|
|
1732
1732
|
link: import_zod5.z.string().optional(),
|
|
1733
1733
|
metafields: import_zod5.z.array(import_zod5.z.object({ key: import_zod5.z.string(), namespace: import_zod5.z.string(), value: import_zod5.z.string() })).optional(),
|
|
1734
1734
|
options: import_zod5.z.array(OfferProductOption),
|
|
@@ -3313,7 +3313,15 @@ var StsCompensationProgressSchema = import_zod42.z.union([
|
|
|
3313
3313
|
AttemptSchema2,
|
|
3314
3314
|
FinishedSchema.extend({ status: import_zod42.z.enum(["removed", "failed", "unknown"]) })
|
|
3315
3315
|
]);
|
|
3316
|
-
var StsReviewReasonSchema = import_zod42.z.enum([
|
|
3316
|
+
var StsReviewReasonSchema = import_zod42.z.enum([
|
|
3317
|
+
"refund-unknown",
|
|
3318
|
+
"compensation-failed",
|
|
3319
|
+
"compensation-unknown",
|
|
3320
|
+
"charge-unconfirmed",
|
|
3321
|
+
"refund-lost",
|
|
3322
|
+
"compensation-lost",
|
|
3323
|
+
"refund-owed"
|
|
3324
|
+
]);
|
|
3317
3325
|
var StsReviewProgressSchema = import_zod42.z.strictObject({
|
|
3318
3326
|
markedAt: PostPurchaseTimestampSchema,
|
|
3319
3327
|
marker: AcceptedMetafieldResultSchema.shape.status,
|
|
@@ -3325,6 +3333,12 @@ var reviewReason = (refund, compensation) => {
|
|
|
3325
3333
|
return `compensation-${compensation.status}`;
|
|
3326
3334
|
return null;
|
|
3327
3335
|
};
|
|
3336
|
+
var judgedReason = (refund, compensation) => {
|
|
3337
|
+
if (refund === null) return "charge-unconfirmed";
|
|
3338
|
+
if (refund.status === "attempted") return "refund-lost";
|
|
3339
|
+
if (compensation?.status === "attempted") return "compensation-lost";
|
|
3340
|
+
return compensation?.status === "removed" ? "refund-owed" : null;
|
|
3341
|
+
};
|
|
3328
3342
|
var StsProgressSchema = import_zod42.z.strictObject({
|
|
3329
3343
|
compensation: StsCompensationProgressSchema.nullable(),
|
|
3330
3344
|
operationId: PurchaseReceiptSchema.shape.operationId,
|
|
@@ -3333,10 +3347,11 @@ var StsProgressSchema = import_zod42.z.strictObject({
|
|
|
3333
3347
|
}).refine(
|
|
3334
3348
|
({ compensation, refund }) => compensation === null || refund?.status === "failed",
|
|
3335
3349
|
"Compensation follows a known refund failure only"
|
|
3336
|
-
).refine(
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
);
|
|
3350
|
+
).refine(({ compensation, refund, review }) => {
|
|
3351
|
+
const resolved = reviewReason(refund, compensation);
|
|
3352
|
+
const reason = review?.reason ?? null;
|
|
3353
|
+
return reason === resolved || resolved === null && reason === judgedReason(refund, compensation);
|
|
3354
|
+
}, "Review must match the unresolved outcome");
|
|
3340
3355
|
|
|
3341
3356
|
// src/schema/postPurchaseFollowups.ts
|
|
3342
3357
|
var RequiredHealthSchema = import_zod43.z.union([
|
|
@@ -4918,8 +4933,10 @@ var PurchaseRecoveryRequestSchema = DecideRequestSchema.strict();
|
|
|
4918
4933
|
var PurchaseRecoveryResponseSchema = import_zod84.z.object({
|
|
4919
4934
|
data: import_zod84.z.object({
|
|
4920
4935
|
/**
|
|
4921
|
-
* Strict, so
|
|
4922
|
-
*
|
|
4936
|
+
* Strict, so rebuy-api (which validates its own reply against this schema) can never emit a field the
|
|
4937
|
+
* contract does not name. cab-post-purchase reads recovery with hand guards that ignore extra keys, so
|
|
4938
|
+
* the strictness never reaches it: release rebuy-api (the pin bump) first, then the client that reads a
|
|
4939
|
+
* field added here.
|
|
4923
4940
|
*/
|
|
4924
4941
|
operation: import_zod84.z.object({
|
|
4925
4942
|
/** Absent from a rebuy-api that predates it. */
|
package/dist/index.mjs
CHANGED
|
@@ -1218,7 +1218,7 @@ var OfferProductDiscount = z5.object({
|
|
|
1218
1218
|
type: z5.string()
|
|
1219
1219
|
});
|
|
1220
1220
|
var OfferProductOption = z5.object({
|
|
1221
|
-
id: z5.number(),
|
|
1221
|
+
id: z5.number().optional(),
|
|
1222
1222
|
name: z5.string(),
|
|
1223
1223
|
position: z5.number(),
|
|
1224
1224
|
values: z5.array(z5.string())
|
|
@@ -1230,7 +1230,7 @@ var OfferProductVariant = z5.object({
|
|
|
1230
1230
|
inventoryManagement: z5.string().nullable(),
|
|
1231
1231
|
inventoryPolicy: z5.string(),
|
|
1232
1232
|
inventoryQuantity: z5.number(),
|
|
1233
|
-
link: z5.string(),
|
|
1233
|
+
link: z5.string().optional(),
|
|
1234
1234
|
option1: z5.string(),
|
|
1235
1235
|
option2: z5.string().nullable(),
|
|
1236
1236
|
option3: z5.string().nullable(),
|
|
@@ -1243,12 +1243,12 @@ var OfferProduct = z5.object({
|
|
|
1243
1243
|
id: z5.number(),
|
|
1244
1244
|
image: z5.object({ src: z5.string() }).nullish(),
|
|
1245
1245
|
/**
|
|
1246
|
-
* `variantIds` is
|
|
1246
|
+
* `variantIds` is nullish: the engine omits `variant_ids` (rather than sending `[]`) on an image tied to no
|
|
1247
1247
|
* variant — seen live on the `recommended` data source while `top_sellers` sends `[]` for the same image
|
|
1248
|
-
* (REB-24958). A missing list means "not variant-specific", exactly like `[]`, so the image
|
|
1249
|
-
* and the product is never dropped over it.
|
|
1248
|
+
* (REB-24958). A missing (or `null`) list means "not variant-specific", exactly like `[]`, so the image
|
|
1249
|
+
* keeps its `src` and the product is never dropped over it.
|
|
1250
1250
|
*/
|
|
1251
|
-
images: z5.array(z5.object({ src: z5.string(), variantIds: z5.array(z5.number()).
|
|
1251
|
+
images: z5.array(z5.object({ src: z5.string(), variantIds: z5.array(z5.number()).nullish() })).nullish(),
|
|
1252
1252
|
link: z5.string().optional(),
|
|
1253
1253
|
metafields: z5.array(z5.object({ key: z5.string(), namespace: z5.string(), value: z5.string() })).optional(),
|
|
1254
1254
|
options: z5.array(OfferProductOption),
|
|
@@ -2833,7 +2833,15 @@ var StsCompensationProgressSchema = z42.union([
|
|
|
2833
2833
|
AttemptSchema2,
|
|
2834
2834
|
FinishedSchema.extend({ status: z42.enum(["removed", "failed", "unknown"]) })
|
|
2835
2835
|
]);
|
|
2836
|
-
var StsReviewReasonSchema = z42.enum([
|
|
2836
|
+
var StsReviewReasonSchema = z42.enum([
|
|
2837
|
+
"refund-unknown",
|
|
2838
|
+
"compensation-failed",
|
|
2839
|
+
"compensation-unknown",
|
|
2840
|
+
"charge-unconfirmed",
|
|
2841
|
+
"refund-lost",
|
|
2842
|
+
"compensation-lost",
|
|
2843
|
+
"refund-owed"
|
|
2844
|
+
]);
|
|
2837
2845
|
var StsReviewProgressSchema = z42.strictObject({
|
|
2838
2846
|
markedAt: PostPurchaseTimestampSchema,
|
|
2839
2847
|
marker: AcceptedMetafieldResultSchema.shape.status,
|
|
@@ -2845,6 +2853,12 @@ var reviewReason = (refund, compensation) => {
|
|
|
2845
2853
|
return `compensation-${compensation.status}`;
|
|
2846
2854
|
return null;
|
|
2847
2855
|
};
|
|
2856
|
+
var judgedReason = (refund, compensation) => {
|
|
2857
|
+
if (refund === null) return "charge-unconfirmed";
|
|
2858
|
+
if (refund.status === "attempted") return "refund-lost";
|
|
2859
|
+
if (compensation?.status === "attempted") return "compensation-lost";
|
|
2860
|
+
return compensation?.status === "removed" ? "refund-owed" : null;
|
|
2861
|
+
};
|
|
2848
2862
|
var StsProgressSchema = z42.strictObject({
|
|
2849
2863
|
compensation: StsCompensationProgressSchema.nullable(),
|
|
2850
2864
|
operationId: PurchaseReceiptSchema.shape.operationId,
|
|
@@ -2853,10 +2867,11 @@ var StsProgressSchema = z42.strictObject({
|
|
|
2853
2867
|
}).refine(
|
|
2854
2868
|
({ compensation, refund }) => compensation === null || refund?.status === "failed",
|
|
2855
2869
|
"Compensation follows a known refund failure only"
|
|
2856
|
-
).refine(
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
);
|
|
2870
|
+
).refine(({ compensation, refund, review }) => {
|
|
2871
|
+
const resolved = reviewReason(refund, compensation);
|
|
2872
|
+
const reason = review?.reason ?? null;
|
|
2873
|
+
return reason === resolved || resolved === null && reason === judgedReason(refund, compensation);
|
|
2874
|
+
}, "Review must match the unresolved outcome");
|
|
2860
2875
|
|
|
2861
2876
|
// src/schema/postPurchaseFollowups.ts
|
|
2862
2877
|
var RequiredHealthSchema = z43.union([
|
|
@@ -4438,8 +4453,10 @@ var PurchaseRecoveryRequestSchema = DecideRequestSchema.strict();
|
|
|
4438
4453
|
var PurchaseRecoveryResponseSchema = z84.object({
|
|
4439
4454
|
data: z84.object({
|
|
4440
4455
|
/**
|
|
4441
|
-
* Strict, so
|
|
4442
|
-
*
|
|
4456
|
+
* Strict, so rebuy-api (which validates its own reply against this schema) can never emit a field the
|
|
4457
|
+
* contract does not name. cab-post-purchase reads recovery with hand guards that ignore extra keys, so
|
|
4458
|
+
* the strictness never reaches it: release rebuy-api (the pin bump) first, then the client that reads a
|
|
4459
|
+
* field added here.
|
|
4443
4460
|
*/
|
|
4444
4461
|
operation: z84.object({
|
|
4445
4462
|
/** Absent from a rebuy-api that predates it. */
|
|
@@ -21,7 +21,7 @@ export declare const OfferProduct: z.ZodObject<{
|
|
|
21
21
|
}, z.core.$strip>>>;
|
|
22
22
|
images: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
23
23
|
src: z.ZodString;
|
|
24
|
-
variantIds: z.ZodOptional<z.ZodArray<z.ZodNumber
|
|
24
|
+
variantIds: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber>>>;
|
|
25
25
|
}, z.core.$strip>>>>;
|
|
26
26
|
link: z.ZodOptional<z.ZodString>;
|
|
27
27
|
metafields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -30,7 +30,7 @@ export declare const OfferProduct: z.ZodObject<{
|
|
|
30
30
|
value: z.ZodString;
|
|
31
31
|
}, z.core.$strip>>>;
|
|
32
32
|
options: z.ZodArray<z.ZodObject<{
|
|
33
|
-
id: z.ZodNumber
|
|
33
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
34
34
|
name: z.ZodString;
|
|
35
35
|
position: z.ZodNumber;
|
|
36
36
|
values: z.ZodArray<z.ZodString>;
|
|
@@ -46,7 +46,7 @@ export declare const OfferProduct: z.ZodObject<{
|
|
|
46
46
|
inventoryManagement: z.ZodNullable<z.ZodString>;
|
|
47
47
|
inventoryPolicy: z.ZodString;
|
|
48
48
|
inventoryQuantity: z.ZodNumber;
|
|
49
|
-
link: z.ZodString
|
|
49
|
+
link: z.ZodOptional<z.ZodString>;
|
|
50
50
|
option1: z.ZodString;
|
|
51
51
|
option2: z.ZodNullable<z.ZodString>;
|
|
52
52
|
option3: z.ZodNullable<z.ZodString>;
|
|
@@ -169,6 +169,10 @@ export declare const AcceptedFollowupsResponseSchema: z.ZodObject<{
|
|
|
169
169
|
"refund-unknown": "refund-unknown";
|
|
170
170
|
"compensation-failed": "compensation-failed";
|
|
171
171
|
"compensation-unknown": "compensation-unknown";
|
|
172
|
+
"charge-unconfirmed": "charge-unconfirmed";
|
|
173
|
+
"refund-lost": "refund-lost";
|
|
174
|
+
"compensation-lost": "compensation-lost";
|
|
175
|
+
"refund-owed": "refund-owed";
|
|
172
176
|
}>;
|
|
173
177
|
}, z.core.$strict>>;
|
|
174
178
|
}, z.core.$strict>>;
|
|
@@ -152,6 +152,10 @@ export declare const PurchaseRecoveryResponseSchema: z.ZodObject<{
|
|
|
152
152
|
"refund-unknown": "refund-unknown";
|
|
153
153
|
"compensation-failed": "compensation-failed";
|
|
154
154
|
"compensation-unknown": "compensation-unknown";
|
|
155
|
+
"charge-unconfirmed": "charge-unconfirmed";
|
|
156
|
+
"refund-lost": "refund-lost";
|
|
157
|
+
"compensation-lost": "compensation-lost";
|
|
158
|
+
"refund-owed": "refund-owed";
|
|
155
159
|
}>;
|
|
156
160
|
}, z.core.$strict>>;
|
|
157
161
|
}, z.core.$strict>>;
|
|
@@ -36,11 +36,25 @@ export declare const StsCompensationProgressSchema: z.ZodUnion<readonly [z.ZodOb
|
|
|
36
36
|
* Why the STS is left for a person; `marker` is the `subscription_rollback_failed` metafield write. A
|
|
37
37
|
* `marker` of `unavailable` means the marker could not be written, NOT that no review is needed: the review
|
|
38
38
|
* stands whatever the marker status.
|
|
39
|
+
*
|
|
40
|
+
* The first three follow from a finished step. The rest are a server's judgement the steps cannot show, so
|
|
41
|
+
* each is allowed (never required) beside one state only:
|
|
42
|
+
* - `charge-unconfirmed`: the server sweep could not confirm the charge (no result reported in time, or an
|
|
43
|
+
* Unknown one), so no step will ever start. Without it a late `/purchases/accepted` reads as not started,
|
|
44
|
+
* which looks pending forever.
|
|
45
|
+
* - `refund-lost` / `compensation-lost`: that step was sent but its outcome was never saved, so it stays
|
|
46
|
+
* `attempted` and may have moved money.
|
|
47
|
+
* - `refund-owed`: the rollback removed the subscription line, but apiv2's removal never refunds, so the
|
|
48
|
+
* buyer may still have been charged for it.
|
|
39
49
|
*/
|
|
40
50
|
export declare const StsReviewReasonSchema: z.ZodEnum<{
|
|
41
51
|
"refund-unknown": "refund-unknown";
|
|
42
52
|
"compensation-failed": "compensation-failed";
|
|
43
53
|
"compensation-unknown": "compensation-unknown";
|
|
54
|
+
"charge-unconfirmed": "charge-unconfirmed";
|
|
55
|
+
"refund-lost": "refund-lost";
|
|
56
|
+
"compensation-lost": "compensation-lost";
|
|
57
|
+
"refund-owed": "refund-owed";
|
|
44
58
|
}>;
|
|
45
59
|
export declare const StsReviewProgressSchema: z.ZodObject<{
|
|
46
60
|
markedAt: z.ZodNumber;
|
|
@@ -56,12 +70,18 @@ export declare const StsReviewProgressSchema: z.ZodObject<{
|
|
|
56
70
|
"refund-unknown": "refund-unknown";
|
|
57
71
|
"compensation-failed": "compensation-failed";
|
|
58
72
|
"compensation-unknown": "compensation-unknown";
|
|
73
|
+
"charge-unconfirmed": "charge-unconfirmed";
|
|
74
|
+
"refund-lost": "refund-lost";
|
|
75
|
+
"compensation-lost": "compensation-lost";
|
|
76
|
+
"refund-owed": "refund-owed";
|
|
59
77
|
}>;
|
|
60
78
|
}, z.core.$strict>;
|
|
61
79
|
/**
|
|
62
80
|
* The refund-compensation saga of one admitted STS, `null` steps not yet started. Compensation follows a
|
|
63
81
|
* known refund failure only, and a review is recorded exactly when the outcome is unresolved (an unknown
|
|
64
|
-
* refund, or a failed or unknown rollback), so a compensated or unresolved STS never reads as switched.
|
|
82
|
+
* refund, or a failed or unknown rollback), so a compensated or unresolved STS never reads as switched. A
|
|
83
|
+
* server may also record a review the steps cannot show (an unconfirmed charge, a lost step, a refund owed
|
|
84
|
+
* after the rollback), only beside the one state it applies to.
|
|
65
85
|
*/
|
|
66
86
|
export declare const StsProgressSchema: z.ZodObject<{
|
|
67
87
|
compensation: z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -107,6 +127,10 @@ export declare const StsProgressSchema: z.ZodObject<{
|
|
|
107
127
|
"refund-unknown": "refund-unknown";
|
|
108
128
|
"compensation-failed": "compensation-failed";
|
|
109
129
|
"compensation-unknown": "compensation-unknown";
|
|
130
|
+
"charge-unconfirmed": "charge-unconfirmed";
|
|
131
|
+
"refund-lost": "refund-lost";
|
|
132
|
+
"compensation-lost": "compensation-lost";
|
|
133
|
+
"refund-owed": "refund-owed";
|
|
110
134
|
}>;
|
|
111
135
|
}, z.core.$strict>>;
|
|
112
136
|
}, z.core.$strict>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type z } from 'zod';
|
|
2
2
|
import { type PostPurchaseOfferEligibility, PostPurchaseOfferEligibilityContext, PostPurchaseStsOptions } from '../schema/postPurchaseOfferEligibility';
|
|
3
|
+
import type { Reporter } from '../server/shared';
|
|
3
4
|
export declare const classifyPostPurchaseOffer: (settings: unknown, response: unknown, context: z.input<typeof PostPurchaseOfferEligibilityContext>,
|
|
4
5
|
/** STS opt-in; absent or `{ sts: false }` keeps every pre-STS outcome. */
|
|
5
|
-
options?: PostPurchaseStsOptions
|
|
6
|
+
options?: PostPurchaseStsOptions,
|
|
7
|
+
/** Hears each purchased STS candidate whose plan data cannot be read, once per response (REB-25050). */
|
|
8
|
+
report?: Reporter) => PostPurchaseOfferEligibility;
|
package/dist/server/index.cjs
CHANGED
|
@@ -81,8 +81,8 @@ var getValidGwpProductIds = (matchedRules = []) => new Set(
|
|
|
81
81
|
|
|
82
82
|
// src/gwp/hasUnsupportedRuleTypes.ts
|
|
83
83
|
var UNSUPPORTED_RULE_TYPES = ["url", "order_tag"];
|
|
84
|
-
var
|
|
85
|
-
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.
|
|
84
|
+
var UNSUPPORTED_RULE_TYPE_FRAGMENT = "geolocation";
|
|
85
|
+
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.includes(UNSUPPORTED_RULE_TYPE_FRAGMENT);
|
|
86
86
|
var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some((group) => (group.rules ?? []).some((item) => isUnsupportedType(item.type)));
|
|
87
87
|
var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
|
|
88
88
|
|
|
@@ -364,7 +364,7 @@ var OfferProductDiscount = import_zod3.z.object({
|
|
|
364
364
|
type: import_zod3.z.string()
|
|
365
365
|
});
|
|
366
366
|
var OfferProductOption = import_zod3.z.object({
|
|
367
|
-
id: import_zod3.z.number(),
|
|
367
|
+
id: import_zod3.z.number().optional(),
|
|
368
368
|
name: import_zod3.z.string(),
|
|
369
369
|
position: import_zod3.z.number(),
|
|
370
370
|
values: import_zod3.z.array(import_zod3.z.string())
|
|
@@ -376,7 +376,7 @@ var OfferProductVariant = import_zod3.z.object({
|
|
|
376
376
|
inventoryManagement: import_zod3.z.string().nullable(),
|
|
377
377
|
inventoryPolicy: import_zod3.z.string(),
|
|
378
378
|
inventoryQuantity: import_zod3.z.number(),
|
|
379
|
-
link: import_zod3.z.string(),
|
|
379
|
+
link: import_zod3.z.string().optional(),
|
|
380
380
|
option1: import_zod3.z.string(),
|
|
381
381
|
option2: import_zod3.z.string().nullable(),
|
|
382
382
|
option3: import_zod3.z.string().nullable(),
|
|
@@ -389,12 +389,12 @@ var OfferProduct = import_zod3.z.object({
|
|
|
389
389
|
id: import_zod3.z.number(),
|
|
390
390
|
image: import_zod3.z.object({ src: import_zod3.z.string() }).nullish(),
|
|
391
391
|
/**
|
|
392
|
-
* `variantIds` is
|
|
392
|
+
* `variantIds` is nullish: the engine omits `variant_ids` (rather than sending `[]`) on an image tied to no
|
|
393
393
|
* variant — seen live on the `recommended` data source while `top_sellers` sends `[]` for the same image
|
|
394
|
-
* (REB-24958). A missing list means "not variant-specific", exactly like `[]`, so the image
|
|
395
|
-
* and the product is never dropped over it.
|
|
394
|
+
* (REB-24958). A missing (or `null`) list means "not variant-specific", exactly like `[]`, so the image
|
|
395
|
+
* keeps its `src` and the product is never dropped over it.
|
|
396
396
|
*/
|
|
397
|
-
images: import_zod3.z.array(import_zod3.z.object({ src: import_zod3.z.string(), variantIds: import_zod3.z.array(import_zod3.z.number()).
|
|
397
|
+
images: import_zod3.z.array(import_zod3.z.object({ src: import_zod3.z.string(), variantIds: import_zod3.z.array(import_zod3.z.number()).nullish() })).nullish(),
|
|
398
398
|
link: import_zod3.z.string().optional(),
|
|
399
399
|
metafields: import_zod3.z.array(import_zod3.z.object({ key: import_zod3.z.string(), namespace: import_zod3.z.string(), value: import_zod3.z.string() })).optional(),
|
|
400
400
|
options: import_zod3.z.array(OfferProductOption),
|
|
@@ -6172,17 +6172,34 @@ var PostPurchaseProductSettings = PostPurchaseSettings.pick({
|
|
|
6172
6172
|
|
|
6173
6173
|
// src/server/readPostPurchaseProductSource.ts
|
|
6174
6174
|
var import_zod45 = require("zod");
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
var Identified = import_zod45.z.object({ id: PostPurchaseProductId });
|
|
6175
|
+
|
|
6176
|
+
// src/server/postPurchaseResponseDrift.ts
|
|
6178
6177
|
var heard = /* @__PURE__ */ new WeakMap();
|
|
6179
|
-
var
|
|
6180
|
-
|
|
6178
|
+
var sources = /* @__PURE__ */ new WeakMap();
|
|
6179
|
+
var rememberPostPurchaseProductSource = (product, response, productIndex) => {
|
|
6180
|
+
sources.set(product, { productIndex, response });
|
|
6181
|
+
};
|
|
6182
|
+
var postPurchaseProductSourceOf = (product) => sources.get(product);
|
|
6183
|
+
var reportOncePerResponse = (response, report, events) => {
|
|
6184
|
+
const reporters = heard.get(response) ?? /* @__PURE__ */ new WeakMap();
|
|
6181
6185
|
heard.set(response, reporters);
|
|
6182
|
-
|
|
6183
|
-
reporters.
|
|
6184
|
-
|
|
6186
|
+
const details = reporters.get(report) ?? /* @__PURE__ */ new Set();
|
|
6187
|
+
reporters.set(report, details);
|
|
6188
|
+
for (const event of events) {
|
|
6189
|
+
if (details.has(event.detail)) continue;
|
|
6190
|
+
details.add(event.detail);
|
|
6191
|
+
report(event);
|
|
6192
|
+
}
|
|
6185
6193
|
};
|
|
6194
|
+
|
|
6195
|
+
// src/server/postPurchaseSellingPlanRequirement.ts
|
|
6196
|
+
var requiresOwnSellingPlan = (product) => product.requiresSellingPlan === true;
|
|
6197
|
+
var requiresUnsuppliedSellingPlan = (product) => requiresOwnSellingPlan(product) && !product.sellingPlanGroups?.length;
|
|
6198
|
+
|
|
6199
|
+
// src/server/readPostPurchaseProductSource.ts
|
|
6200
|
+
var Envelope = PostPurchaseProductSource.extend({ data: import_zod45.z.array(import_zod45.z.unknown()) });
|
|
6201
|
+
var Product3 = PostPurchaseProductSource.shape.data.element;
|
|
6202
|
+
var Identified = import_zod45.z.object({ id: PostPurchaseProductId });
|
|
6186
6203
|
var readPostPurchaseProductSource = (response, report) => {
|
|
6187
6204
|
const { data, metadata } = Envelope.parse(deepCamelCase(response));
|
|
6188
6205
|
const drift = [];
|
|
@@ -6190,9 +6207,10 @@ var readPostPurchaseProductSource = (response, report) => {
|
|
|
6190
6207
|
const products = data.flatMap((raw, productIndex) => {
|
|
6191
6208
|
const parsed = Product3.safeParse(raw);
|
|
6192
6209
|
if (parsed.success) {
|
|
6193
|
-
|
|
6210
|
+
rememberPostPurchaseProductSource(parsed.data, response, productIndex);
|
|
6211
|
+
if (requiresUnsuppliedSellingPlan(parsed.data))
|
|
6194
6212
|
drift.push({
|
|
6195
|
-
detail: `
|
|
6213
|
+
detail: `Ineligible post-purchase product ${parsed.data.id}: data.${productIndex}.requiresSellingPlan`,
|
|
6196
6214
|
kind: "product"
|
|
6197
6215
|
});
|
|
6198
6216
|
return [{ product: parsed.data, productIndex }];
|
|
@@ -6207,7 +6225,7 @@ var readPostPurchaseProductSource = (response, report) => {
|
|
|
6207
6225
|
});
|
|
6208
6226
|
return [];
|
|
6209
6227
|
});
|
|
6210
|
-
if (report && drift.length
|
|
6228
|
+
if (report && drift.length) reportOncePerResponse(response, report, drift);
|
|
6211
6229
|
return { dropped, metadata, products };
|
|
6212
6230
|
};
|
|
6213
6231
|
|
|
@@ -6353,7 +6371,8 @@ var Groups = import_zod52.z.array(
|
|
|
6353
6371
|
sellingPlans: import_zod52.z.unknown()
|
|
6354
6372
|
})
|
|
6355
6373
|
);
|
|
6356
|
-
var
|
|
6374
|
+
var PostPurchasePlanAllocations = import_zod52.z.array(import_zod52.z.object({ sellingPlanGroupId: import_zod52.z.string().min(1) }));
|
|
6375
|
+
var Allocations = PostPurchasePlanAllocations.default([]);
|
|
6357
6376
|
var Plans = import_zod52.z.array(
|
|
6358
6377
|
PostPurchasePlanOption.pick({ id: true, name: true }).extend({
|
|
6359
6378
|
options: import_zod52.z.array(import_zod52.z.object({ value: import_zod52.z.string().min(1) })).min(1),
|
|
@@ -6382,19 +6401,20 @@ var readPostPurchaseModernPlans = (raw, allocations) => {
|
|
|
6382
6401
|
};
|
|
6383
6402
|
|
|
6384
6403
|
// src/server/postPurchasePlanCatalog.ts
|
|
6404
|
+
var hasLegacyPlanGroups = ({ sellingPlanGroups }) => typeof sellingPlanGroups[0]?.id === "number";
|
|
6385
6405
|
var projectPostPurchasePlanCatalog = (normalized, report) => {
|
|
6386
6406
|
const { data } = normalized;
|
|
6387
|
-
if (data.length === 1 && data[0].variants.length > 1) return null;
|
|
6388
6407
|
if (normalized.emptiedByDrop && !data.length) return null;
|
|
6408
|
+
if (data.length === 1 && data[0].variants.length > 1) return null;
|
|
6389
6409
|
try {
|
|
6390
|
-
if (data.length !== 1 || data[0].variants.length !== 1) throw new Error("
|
|
6410
|
+
if (data.length !== 1 || data[0].variants.length !== 1) throw new Error("No single product and variant");
|
|
6391
6411
|
const product = data[0];
|
|
6392
6412
|
const variant2 = product.variants[0];
|
|
6393
6413
|
if (variant2.productId !== product.id) throw new Error("Foreign variant");
|
|
6394
6414
|
const groups = product.sellingPlanGroups;
|
|
6395
6415
|
const required = groups.map((group) => group.requiresSellingPlan);
|
|
6396
6416
|
if (required.some((flag) => flag !== required[0])) throw new Error("Conflicting requirement");
|
|
6397
|
-
const plans =
|
|
6417
|
+
const plans = hasLegacyPlanGroups(product) ? readPostPurchaseLegacyPlans(groups[0], variant2.id) : readPostPurchaseModernPlans(groups, variant2.sellingPlanAllocations);
|
|
6398
6418
|
const unique = /* @__PURE__ */ new Map();
|
|
6399
6419
|
for (const plan of plans) {
|
|
6400
6420
|
const previous = unique.get(plan.id);
|
|
@@ -6407,8 +6427,7 @@ var projectPostPurchasePlanCatalog = (normalized, report) => {
|
|
|
6407
6427
|
discountPercentage: postPurchasePlanDiscountPercentage(variant2.price, plan.adjustment)
|
|
6408
6428
|
})),
|
|
6409
6429
|
productId: product.id,
|
|
6410
|
-
|
|
6411
|
-
requiresSellingPlan: (required[0] ?? false) || product.requiresSellingPlan === true,
|
|
6430
|
+
requiresSellingPlan: (required[0] ?? false) || requiresOwnSellingPlan(product),
|
|
6412
6431
|
variantId: variant2.id
|
|
6413
6432
|
});
|
|
6414
6433
|
} catch {
|
|
@@ -6961,7 +6980,7 @@ var oneTimeUnavailable = (normalized, allowCollection, quantity3) => {
|
|
|
6961
6980
|
return "Only fixed one-time configuration is supported";
|
|
6962
6981
|
const product = data[0];
|
|
6963
6982
|
const variant2 = product.variants[0];
|
|
6964
|
-
if (product.productGroupVariants.length || product
|
|
6983
|
+
if (product.productGroupVariants.length || requiresOwnSellingPlan(product) || product.sellingPlanGroups?.some((group) => group.requiresSellingPlan))
|
|
6965
6984
|
return "A one-time variant without product groups or a required selling plan is required";
|
|
6966
6985
|
if (!postPurchaseVariantAvailable(variant2)) return VARIANT_UNAVAILABLE;
|
|
6967
6986
|
if (variant2.productId !== product.id) throw new Error("Product variant identity mismatch");
|
|
@@ -7624,18 +7643,30 @@ var readPostPurchaseQuantity = ({
|
|
|
7624
7643
|
|
|
7625
7644
|
// src/server/postPurchaseSts.ts
|
|
7626
7645
|
var stsAllowed = ({ effective, original }) => [original, effective].some((config) => config.upgradeToSubscription.allow);
|
|
7627
|
-
var
|
|
7646
|
+
var reportUnreadableStsLine = (product, field, report) => {
|
|
7647
|
+
const source2 = postPurchaseProductSourceOf(product);
|
|
7648
|
+
const path = source2 ? `data.${source2.productIndex}.${field}` : field;
|
|
7649
|
+
const detail = `Unreadable post-purchase STS product ${product.id}: ${path}`;
|
|
7650
|
+
reportOncePerResponse(source2?.response ?? product, report, [{ detail, kind: "product" }]);
|
|
7651
|
+
};
|
|
7652
|
+
var postPurchaseStsLine = (normalized, product, lines, variantId, report) => {
|
|
7628
7653
|
if (!product.sellingPlanGroups?.length || product.productGroupVariants.length) return void 0;
|
|
7629
|
-
|
|
7654
|
+
const modern = !hasLegacyPlanGroups(product);
|
|
7655
|
+
for (const [index, variant2] of product.variants.entries()) {
|
|
7630
7656
|
const matched = lines.filter((line) => line.sellingPlanId === void 0 && line.variantId === variant2.id);
|
|
7631
7657
|
const data = [{ ...product, variants: [variant2] }];
|
|
7632
|
-
if ((variantId === void 0 || variant2.id === variantId) && matched.length === 1 && postPurchaseVariantAvailable(variant2)
|
|
7633
|
-
|
|
7658
|
+
if ((variantId === void 0 || variant2.id === variantId) && matched.length === 1 && postPurchaseVariantAvailable(variant2)) {
|
|
7659
|
+
const unread = [];
|
|
7660
|
+
if (projectPostPurchasePlanCatalog({ ...normalized, data }, (event) => unread.push(event))?.plans.length)
|
|
7661
|
+
return { data, line: matched[0] };
|
|
7662
|
+
const field = modern && !PostPurchasePlanAllocations.safeParse(variant2.sellingPlanAllocations).success ? `variants.${index}.sellingPlanAllocations` : !unread.length ? void 0 : variant2.productId === product.id ? "sellingPlanGroups" : `variants.${index}.productId`;
|
|
7663
|
+
if (report && field) reportUnreadableStsLine(product, field, report);
|
|
7664
|
+
}
|
|
7634
7665
|
}
|
|
7635
7666
|
};
|
|
7636
7667
|
var preparePostPurchaseStsView = (widgetId, normalized, product, context, requested, report) => {
|
|
7637
7668
|
const { originalLineItems } = PostPurchaseStsContext.parse(context);
|
|
7638
|
-
const found = postPurchaseStsLine(normalized, product, originalLineItems, requested.variantId);
|
|
7669
|
+
const found = postPurchaseStsLine(normalized, product, originalLineItems, requested.variantId, report);
|
|
7639
7670
|
if (!found) return null;
|
|
7640
7671
|
const whole = found.line.quantity;
|
|
7641
7672
|
if (requested.quantity !== void 0 && requested.quantity !== whole)
|
|
@@ -8070,7 +8101,7 @@ var eligibility = (normalized, product, excluded) => {
|
|
|
8070
8101
|
if (product.productGroupVariants.length) {
|
|
8071
8102
|
const group = readPostPurchaseGroup(product, normalized.effective, excluded);
|
|
8072
8103
|
if (!group.entries.length) return void 0;
|
|
8073
|
-
return group.entries.map(({ product: option }) =>
|
|
8104
|
+
return group.entries.map(({ product: option }) => eligibility(normalized, option)).some(Boolean);
|
|
8074
8105
|
}
|
|
8075
8106
|
if (!product.variants.length) return false;
|
|
8076
8107
|
const variants = projectPostPurchaseVariantCatalog(product, normalized.effective).variants;
|
|
@@ -8097,7 +8128,7 @@ var postPurchaseMemberEligibility = (normalized, product, excluded) => {
|
|
|
8097
8128
|
|
|
8098
8129
|
// src/server/classifyPostPurchaseOffer.ts
|
|
8099
8130
|
var identities = (members) => members.map(({ product, productIndex }) => ({ productId: product.id, productIndex }));
|
|
8100
|
-
var classifyPostPurchaseOffer = (settings, response, context, options2) => {
|
|
8131
|
+
var classifyPostPurchaseOffer = (settings, response, context, options2, report) => {
|
|
8101
8132
|
let unreadable = "context-unreadable";
|
|
8102
8133
|
try {
|
|
8103
8134
|
const trusted = PostPurchaseOfferEligibilityContext.parse(context);
|
|
@@ -8123,7 +8154,7 @@ var classifyPostPurchaseOffer = (settings, response, context, options2) => {
|
|
|
8123
8154
|
PostPurchaseSubscriptionSettings.parse(normalized.effective.subscription);
|
|
8124
8155
|
}
|
|
8125
8156
|
const lines = trusted.originalLineItems;
|
|
8126
|
-
const stsMembers = sts ? members.filter(({ product }) => postPurchaseStsLine(normalized, product, lines)) : [];
|
|
8157
|
+
const stsMembers = sts ? members.filter(({ product }) => postPurchaseStsLine(normalized, product, lines, void 0, report)) : [];
|
|
8127
8158
|
if (stsMembers.length) return { members: identities(stsMembers), mode: "sts", status: "retain" };
|
|
8128
8159
|
const classified = members.map((member) => ({
|
|
8129
8160
|
...member,
|
package/dist/server/index.mjs
CHANGED
|
@@ -8,8 +8,8 @@ var getValidGwpProductIds = (matchedRules = []) => new Set(
|
|
|
8
8
|
|
|
9
9
|
// src/gwp/hasUnsupportedRuleTypes.ts
|
|
10
10
|
var UNSUPPORTED_RULE_TYPES = ["url", "order_tag"];
|
|
11
|
-
var
|
|
12
|
-
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.
|
|
11
|
+
var UNSUPPORTED_RULE_TYPE_FRAGMENT = "geolocation";
|
|
12
|
+
var isUnsupportedType = (type = "") => UNSUPPORTED_RULE_TYPES.includes(type) || type.includes(UNSUPPORTED_RULE_TYPE_FRAGMENT);
|
|
13
13
|
var ruleHasUnsupportedType = (rule) => (rule.logic ?? []).some((group) => (group.rules ?? []).some((item) => isUnsupportedType(item.type)));
|
|
14
14
|
var hasUnsupportedRuleTypes = (metadata) => [...metadata?.matchedRules ?? [], ...metadata?.unmatchedRules ?? []].some(ruleHasUnsupportedType);
|
|
15
15
|
|
|
@@ -291,7 +291,7 @@ var OfferProductDiscount = z3.object({
|
|
|
291
291
|
type: z3.string()
|
|
292
292
|
});
|
|
293
293
|
var OfferProductOption = z3.object({
|
|
294
|
-
id: z3.number(),
|
|
294
|
+
id: z3.number().optional(),
|
|
295
295
|
name: z3.string(),
|
|
296
296
|
position: z3.number(),
|
|
297
297
|
values: z3.array(z3.string())
|
|
@@ -303,7 +303,7 @@ var OfferProductVariant = z3.object({
|
|
|
303
303
|
inventoryManagement: z3.string().nullable(),
|
|
304
304
|
inventoryPolicy: z3.string(),
|
|
305
305
|
inventoryQuantity: z3.number(),
|
|
306
|
-
link: z3.string(),
|
|
306
|
+
link: z3.string().optional(),
|
|
307
307
|
option1: z3.string(),
|
|
308
308
|
option2: z3.string().nullable(),
|
|
309
309
|
option3: z3.string().nullable(),
|
|
@@ -316,12 +316,12 @@ var OfferProduct = z3.object({
|
|
|
316
316
|
id: z3.number(),
|
|
317
317
|
image: z3.object({ src: z3.string() }).nullish(),
|
|
318
318
|
/**
|
|
319
|
-
* `variantIds` is
|
|
319
|
+
* `variantIds` is nullish: the engine omits `variant_ids` (rather than sending `[]`) on an image tied to no
|
|
320
320
|
* variant — seen live on the `recommended` data source while `top_sellers` sends `[]` for the same image
|
|
321
|
-
* (REB-24958). A missing list means "not variant-specific", exactly like `[]`, so the image
|
|
322
|
-
* and the product is never dropped over it.
|
|
321
|
+
* (REB-24958). A missing (or `null`) list means "not variant-specific", exactly like `[]`, so the image
|
|
322
|
+
* keeps its `src` and the product is never dropped over it.
|
|
323
323
|
*/
|
|
324
|
-
images: z3.array(z3.object({ src: z3.string(), variantIds: z3.array(z3.number()).
|
|
324
|
+
images: z3.array(z3.object({ src: z3.string(), variantIds: z3.array(z3.number()).nullish() })).nullish(),
|
|
325
325
|
link: z3.string().optional(),
|
|
326
326
|
metafields: z3.array(z3.object({ key: z3.string(), namespace: z3.string(), value: z3.string() })).optional(),
|
|
327
327
|
options: z3.array(OfferProductOption),
|
|
@@ -6099,17 +6099,34 @@ var PostPurchaseProductSettings = PostPurchaseSettings.pick({
|
|
|
6099
6099
|
|
|
6100
6100
|
// src/server/readPostPurchaseProductSource.ts
|
|
6101
6101
|
import { z as z45 } from "zod";
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
var Identified = z45.object({ id: PostPurchaseProductId });
|
|
6102
|
+
|
|
6103
|
+
// src/server/postPurchaseResponseDrift.ts
|
|
6105
6104
|
var heard = /* @__PURE__ */ new WeakMap();
|
|
6106
|
-
var
|
|
6107
|
-
|
|
6105
|
+
var sources = /* @__PURE__ */ new WeakMap();
|
|
6106
|
+
var rememberPostPurchaseProductSource = (product, response, productIndex) => {
|
|
6107
|
+
sources.set(product, { productIndex, response });
|
|
6108
|
+
};
|
|
6109
|
+
var postPurchaseProductSourceOf = (product) => sources.get(product);
|
|
6110
|
+
var reportOncePerResponse = (response, report, events) => {
|
|
6111
|
+
const reporters = heard.get(response) ?? /* @__PURE__ */ new WeakMap();
|
|
6108
6112
|
heard.set(response, reporters);
|
|
6109
|
-
|
|
6110
|
-
reporters.
|
|
6111
|
-
|
|
6113
|
+
const details = reporters.get(report) ?? /* @__PURE__ */ new Set();
|
|
6114
|
+
reporters.set(report, details);
|
|
6115
|
+
for (const event of events) {
|
|
6116
|
+
if (details.has(event.detail)) continue;
|
|
6117
|
+
details.add(event.detail);
|
|
6118
|
+
report(event);
|
|
6119
|
+
}
|
|
6112
6120
|
};
|
|
6121
|
+
|
|
6122
|
+
// src/server/postPurchaseSellingPlanRequirement.ts
|
|
6123
|
+
var requiresOwnSellingPlan = (product) => product.requiresSellingPlan === true;
|
|
6124
|
+
var requiresUnsuppliedSellingPlan = (product) => requiresOwnSellingPlan(product) && !product.sellingPlanGroups?.length;
|
|
6125
|
+
|
|
6126
|
+
// src/server/readPostPurchaseProductSource.ts
|
|
6127
|
+
var Envelope = PostPurchaseProductSource.extend({ data: z45.array(z45.unknown()) });
|
|
6128
|
+
var Product3 = PostPurchaseProductSource.shape.data.element;
|
|
6129
|
+
var Identified = z45.object({ id: PostPurchaseProductId });
|
|
6113
6130
|
var readPostPurchaseProductSource = (response, report) => {
|
|
6114
6131
|
const { data, metadata } = Envelope.parse(deepCamelCase(response));
|
|
6115
6132
|
const drift = [];
|
|
@@ -6117,9 +6134,10 @@ var readPostPurchaseProductSource = (response, report) => {
|
|
|
6117
6134
|
const products = data.flatMap((raw, productIndex) => {
|
|
6118
6135
|
const parsed = Product3.safeParse(raw);
|
|
6119
6136
|
if (parsed.success) {
|
|
6120
|
-
|
|
6137
|
+
rememberPostPurchaseProductSource(parsed.data, response, productIndex);
|
|
6138
|
+
if (requiresUnsuppliedSellingPlan(parsed.data))
|
|
6121
6139
|
drift.push({
|
|
6122
|
-
detail: `
|
|
6140
|
+
detail: `Ineligible post-purchase product ${parsed.data.id}: data.${productIndex}.requiresSellingPlan`,
|
|
6123
6141
|
kind: "product"
|
|
6124
6142
|
});
|
|
6125
6143
|
return [{ product: parsed.data, productIndex }];
|
|
@@ -6134,7 +6152,7 @@ var readPostPurchaseProductSource = (response, report) => {
|
|
|
6134
6152
|
});
|
|
6135
6153
|
return [];
|
|
6136
6154
|
});
|
|
6137
|
-
if (report && drift.length
|
|
6155
|
+
if (report && drift.length) reportOncePerResponse(response, report, drift);
|
|
6138
6156
|
return { dropped, metadata, products };
|
|
6139
6157
|
};
|
|
6140
6158
|
|
|
@@ -6280,7 +6298,8 @@ var Groups = z52.array(
|
|
|
6280
6298
|
sellingPlans: z52.unknown()
|
|
6281
6299
|
})
|
|
6282
6300
|
);
|
|
6283
|
-
var
|
|
6301
|
+
var PostPurchasePlanAllocations = z52.array(z52.object({ sellingPlanGroupId: z52.string().min(1) }));
|
|
6302
|
+
var Allocations = PostPurchasePlanAllocations.default([]);
|
|
6284
6303
|
var Plans = z52.array(
|
|
6285
6304
|
PostPurchasePlanOption.pick({ id: true, name: true }).extend({
|
|
6286
6305
|
options: z52.array(z52.object({ value: z52.string().min(1) })).min(1),
|
|
@@ -6309,19 +6328,20 @@ var readPostPurchaseModernPlans = (raw, allocations) => {
|
|
|
6309
6328
|
};
|
|
6310
6329
|
|
|
6311
6330
|
// src/server/postPurchasePlanCatalog.ts
|
|
6331
|
+
var hasLegacyPlanGroups = ({ sellingPlanGroups }) => typeof sellingPlanGroups[0]?.id === "number";
|
|
6312
6332
|
var projectPostPurchasePlanCatalog = (normalized, report) => {
|
|
6313
6333
|
const { data } = normalized;
|
|
6314
|
-
if (data.length === 1 && data[0].variants.length > 1) return null;
|
|
6315
6334
|
if (normalized.emptiedByDrop && !data.length) return null;
|
|
6335
|
+
if (data.length === 1 && data[0].variants.length > 1) return null;
|
|
6316
6336
|
try {
|
|
6317
|
-
if (data.length !== 1 || data[0].variants.length !== 1) throw new Error("
|
|
6337
|
+
if (data.length !== 1 || data[0].variants.length !== 1) throw new Error("No single product and variant");
|
|
6318
6338
|
const product = data[0];
|
|
6319
6339
|
const variant2 = product.variants[0];
|
|
6320
6340
|
if (variant2.productId !== product.id) throw new Error("Foreign variant");
|
|
6321
6341
|
const groups = product.sellingPlanGroups;
|
|
6322
6342
|
const required = groups.map((group) => group.requiresSellingPlan);
|
|
6323
6343
|
if (required.some((flag) => flag !== required[0])) throw new Error("Conflicting requirement");
|
|
6324
|
-
const plans =
|
|
6344
|
+
const plans = hasLegacyPlanGroups(product) ? readPostPurchaseLegacyPlans(groups[0], variant2.id) : readPostPurchaseModernPlans(groups, variant2.sellingPlanAllocations);
|
|
6325
6345
|
const unique = /* @__PURE__ */ new Map();
|
|
6326
6346
|
for (const plan of plans) {
|
|
6327
6347
|
const previous = unique.get(plan.id);
|
|
@@ -6334,8 +6354,7 @@ var projectPostPurchasePlanCatalog = (normalized, report) => {
|
|
|
6334
6354
|
discountPercentage: postPurchasePlanDiscountPercentage(variant2.price, plan.adjustment)
|
|
6335
6355
|
})),
|
|
6336
6356
|
productId: product.id,
|
|
6337
|
-
|
|
6338
|
-
requiresSellingPlan: (required[0] ?? false) || product.requiresSellingPlan === true,
|
|
6357
|
+
requiresSellingPlan: (required[0] ?? false) || requiresOwnSellingPlan(product),
|
|
6339
6358
|
variantId: variant2.id
|
|
6340
6359
|
});
|
|
6341
6360
|
} catch {
|
|
@@ -6888,7 +6907,7 @@ var oneTimeUnavailable = (normalized, allowCollection, quantity3) => {
|
|
|
6888
6907
|
return "Only fixed one-time configuration is supported";
|
|
6889
6908
|
const product = data[0];
|
|
6890
6909
|
const variant2 = product.variants[0];
|
|
6891
|
-
if (product.productGroupVariants.length || product
|
|
6910
|
+
if (product.productGroupVariants.length || requiresOwnSellingPlan(product) || product.sellingPlanGroups?.some((group) => group.requiresSellingPlan))
|
|
6892
6911
|
return "A one-time variant without product groups or a required selling plan is required";
|
|
6893
6912
|
if (!postPurchaseVariantAvailable(variant2)) return VARIANT_UNAVAILABLE;
|
|
6894
6913
|
if (variant2.productId !== product.id) throw new Error("Product variant identity mismatch");
|
|
@@ -7551,18 +7570,30 @@ var readPostPurchaseQuantity = ({
|
|
|
7551
7570
|
|
|
7552
7571
|
// src/server/postPurchaseSts.ts
|
|
7553
7572
|
var stsAllowed = ({ effective, original }) => [original, effective].some((config) => config.upgradeToSubscription.allow);
|
|
7554
|
-
var
|
|
7573
|
+
var reportUnreadableStsLine = (product, field, report) => {
|
|
7574
|
+
const source2 = postPurchaseProductSourceOf(product);
|
|
7575
|
+
const path = source2 ? `data.${source2.productIndex}.${field}` : field;
|
|
7576
|
+
const detail = `Unreadable post-purchase STS product ${product.id}: ${path}`;
|
|
7577
|
+
reportOncePerResponse(source2?.response ?? product, report, [{ detail, kind: "product" }]);
|
|
7578
|
+
};
|
|
7579
|
+
var postPurchaseStsLine = (normalized, product, lines, variantId, report) => {
|
|
7555
7580
|
if (!product.sellingPlanGroups?.length || product.productGroupVariants.length) return void 0;
|
|
7556
|
-
|
|
7581
|
+
const modern = !hasLegacyPlanGroups(product);
|
|
7582
|
+
for (const [index, variant2] of product.variants.entries()) {
|
|
7557
7583
|
const matched = lines.filter((line) => line.sellingPlanId === void 0 && line.variantId === variant2.id);
|
|
7558
7584
|
const data = [{ ...product, variants: [variant2] }];
|
|
7559
|
-
if ((variantId === void 0 || variant2.id === variantId) && matched.length === 1 && postPurchaseVariantAvailable(variant2)
|
|
7560
|
-
|
|
7585
|
+
if ((variantId === void 0 || variant2.id === variantId) && matched.length === 1 && postPurchaseVariantAvailable(variant2)) {
|
|
7586
|
+
const unread = [];
|
|
7587
|
+
if (projectPostPurchasePlanCatalog({ ...normalized, data }, (event) => unread.push(event))?.plans.length)
|
|
7588
|
+
return { data, line: matched[0] };
|
|
7589
|
+
const field = modern && !PostPurchasePlanAllocations.safeParse(variant2.sellingPlanAllocations).success ? `variants.${index}.sellingPlanAllocations` : !unread.length ? void 0 : variant2.productId === product.id ? "sellingPlanGroups" : `variants.${index}.productId`;
|
|
7590
|
+
if (report && field) reportUnreadableStsLine(product, field, report);
|
|
7591
|
+
}
|
|
7561
7592
|
}
|
|
7562
7593
|
};
|
|
7563
7594
|
var preparePostPurchaseStsView = (widgetId, normalized, product, context, requested, report) => {
|
|
7564
7595
|
const { originalLineItems } = PostPurchaseStsContext.parse(context);
|
|
7565
|
-
const found = postPurchaseStsLine(normalized, product, originalLineItems, requested.variantId);
|
|
7596
|
+
const found = postPurchaseStsLine(normalized, product, originalLineItems, requested.variantId, report);
|
|
7566
7597
|
if (!found) return null;
|
|
7567
7598
|
const whole = found.line.quantity;
|
|
7568
7599
|
if (requested.quantity !== void 0 && requested.quantity !== whole)
|
|
@@ -7997,7 +8028,7 @@ var eligibility = (normalized, product, excluded) => {
|
|
|
7997
8028
|
if (product.productGroupVariants.length) {
|
|
7998
8029
|
const group = readPostPurchaseGroup(product, normalized.effective, excluded);
|
|
7999
8030
|
if (!group.entries.length) return void 0;
|
|
8000
|
-
return group.entries.map(({ product: option }) =>
|
|
8031
|
+
return group.entries.map(({ product: option }) => eligibility(normalized, option)).some(Boolean);
|
|
8001
8032
|
}
|
|
8002
8033
|
if (!product.variants.length) return false;
|
|
8003
8034
|
const variants = projectPostPurchaseVariantCatalog(product, normalized.effective).variants;
|
|
@@ -8024,7 +8055,7 @@ var postPurchaseMemberEligibility = (normalized, product, excluded) => {
|
|
|
8024
8055
|
|
|
8025
8056
|
// src/server/classifyPostPurchaseOffer.ts
|
|
8026
8057
|
var identities = (members) => members.map(({ product, productIndex }) => ({ productId: product.id, productIndex }));
|
|
8027
|
-
var classifyPostPurchaseOffer = (settings, response, context, options2) => {
|
|
8058
|
+
var classifyPostPurchaseOffer = (settings, response, context, options2, report) => {
|
|
8028
8059
|
let unreadable = "context-unreadable";
|
|
8029
8060
|
try {
|
|
8030
8061
|
const trusted = PostPurchaseOfferEligibilityContext.parse(context);
|
|
@@ -8050,7 +8081,7 @@ var classifyPostPurchaseOffer = (settings, response, context, options2) => {
|
|
|
8050
8081
|
PostPurchaseSubscriptionSettings.parse(normalized.effective.subscription);
|
|
8051
8082
|
}
|
|
8052
8083
|
const lines = trusted.originalLineItems;
|
|
8053
|
-
const stsMembers = sts ? members.filter(({ product }) => postPurchaseStsLine(normalized, product, lines)) : [];
|
|
8084
|
+
const stsMembers = sts ? members.filter(({ product }) => postPurchaseStsLine(normalized, product, lines, void 0, report)) : [];
|
|
8054
8085
|
if (stsMembers.length) return { members: identities(stsMembers), mode: "sts", status: "retain" };
|
|
8055
8086
|
const classified = members.map((member) => ({
|
|
8056
8087
|
...member,
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { PostPurchasePlanOption } from '../schema/postPurchasePlanCatalog';
|
|
3
|
+
/** A variant's readable `selling_plan_allocations`; a missing list reads as none here, but STS reports it. */
|
|
4
|
+
export declare const PostPurchasePlanAllocations: z.ZodArray<z.ZodObject<{
|
|
5
|
+
sellingPlanGroupId: z.ZodString;
|
|
6
|
+
}, z.core.$strip>>;
|
|
2
7
|
/**
|
|
3
8
|
* Match allocation groups in product order, then preserve each matching group's plan order. Membership is
|
|
4
9
|
* group-level by design (frozen-branch parity): Shopify allocates every plan of a group to a member variant, and
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { PostPurchasePlanCatalog } from '../schema/postPurchasePlanCatalog';
|
|
2
2
|
import type { normalizePostPurchaseProduct } from '../server/normalizePostPurchaseProduct';
|
|
3
3
|
import type { Reporter } from '../server/shared';
|
|
4
|
+
type Product = ReturnType<typeof normalizePostPurchaseProduct>['data'][number];
|
|
5
|
+
/** v1 selling-plan groups carry numeric ids and their plans on the group; modern ones key plans per variant. */
|
|
6
|
+
export declare const hasLegacyPlanGroups: ({ sellingPlanGroups }: Pick<Product, "sellingPlanGroups">) => boolean;
|
|
4
7
|
/**
|
|
5
8
|
* Optional inspection data survives unsupported preparation without changing its purchase authority. Each plan
|
|
6
9
|
* carries its own display saving on this variant's price, so the switch label needs no client maths.
|
|
@@ -12,3 +15,4 @@ import type { Reporter } from '../server/shared';
|
|
|
12
15
|
* not a second fault: each drop was already reported, so it answers null without another report.
|
|
13
16
|
*/
|
|
14
17
|
export declare const projectPostPurchasePlanCatalog: (normalized: ReturnType<typeof normalizePostPurchaseProduct>, report?: Reporter) => PostPurchasePlanCatalog | null;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Reporter, ServerDrift } from '../server/shared';
|
|
2
|
+
/** Records where a parsed product came from, so drift found about it later still names its response and path. */
|
|
3
|
+
export declare const rememberPostPurchaseProductSource: (product: object, response: object, productIndex: number) => void;
|
|
4
|
+
/** The response and raw `productIndex` a parsed product was read from, when it was read by the product source. */
|
|
5
|
+
export declare const postPurchaseProductSourceOf: (product: object) => {
|
|
6
|
+
productIndex: number;
|
|
7
|
+
response: object;
|
|
8
|
+
} | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Reports each event `report` has not yet heard about `response`, keyed on the `response` object itself: every
|
|
11
|
+
* projection of one request rereads the same object, so a drift is heard once however many projections find it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const reportOncePerResponse: (response: object, report: Reporter, events: ServerDrift[]) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type Requiring = {
|
|
2
|
+
requiresSellingPlan?: boolean | null;
|
|
3
|
+
sellingPlanGroups?: unknown[];
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* REB-25048: a v2 product flags `requires_selling_plan` on itself rather than on its selling plan groups. It is
|
|
7
|
+
* never sold one-time, and a product that also has groups is offered subscription-only. Legacy read only the first
|
|
8
|
+
* group's flag, so under Product Type `both` it dropped such a product where the rebuild offers it subscription-only
|
|
9
|
+
* (accepted deviation, connscoding on npm-rebuy #117: neither sells it one-time).
|
|
10
|
+
*/
|
|
11
|
+
export declare const requiresOwnSellingPlan: (product: Requiring) => boolean;
|
|
12
|
+
/** Requires a selling plan with no group to supply one: no purchasable mode is left, so eligibility drops it. */
|
|
13
|
+
export declare const requiresUnsuppliedSellingPlan: (product: Requiring) => boolean;
|
|
14
|
+
export {};
|
|
@@ -17,9 +17,17 @@ export declare const stsAllowed: ({ effective, original }: Pick<Normalized, "eff
|
|
|
17
17
|
* plan; (2) it sits on exactly one one-time line, since two make the refunded quantity ambiguous; (3) a grouped
|
|
18
18
|
* product is never an STS member; (4) the bound variant is the purchased line's, not the product's selected
|
|
19
19
|
* variant, and subscription is the only mode. The first such variant in catalog order wins, unless `variantId`
|
|
20
|
-
* names one.
|
|
20
|
+
* names one. Two more differences from legacy are accepted (connscoding, npm-rebuy #109): a subscription line
|
|
21
|
+
* beside a one-time line of the same variant still gets STS, where legacy's first match on the variant offered
|
|
22
|
+
* none; and the quantity is fixed at the whole one-time line, where legacy let the buyer change it.
|
|
23
|
+
*
|
|
24
|
+
* `report` hears a candidate (a purchased, in-stock variant on exactly one one-time line) whose plan data cannot be
|
|
25
|
+
* read, since it then silently switches nothing: a modern selling-plan variant whose `sellingPlanAllocations` is
|
|
26
|
+
* missing or malformed, or plan data the catalog cannot read. Each is a `product` drift naming the product id and
|
|
27
|
+
* the dotted path from the source root, never a value, heard once per response like the source's own drops. A
|
|
28
|
+
* readable variant that simply has no plan, or no candidate at all, stays silent.
|
|
21
29
|
*/
|
|
22
|
-
export declare const postPurchaseStsLine: <T extends Line>(normalized: Normalized, product: Normalized["data"][number], lines: T[], variantId?: number) => {
|
|
30
|
+
export declare const postPurchaseStsLine: <T extends Line>(normalized: Normalized, product: Normalized["data"][number], lines: T[], variantId?: number, report?: Reporter) => {
|
|
23
31
|
data: {
|
|
24
32
|
variants: {
|
|
25
33
|
title: string;
|
package/dist/server/shared.d.ts
CHANGED
|
@@ -28,12 +28,12 @@ export type UpstreamFetch = (path: string, params: Record<string, string>, optio
|
|
|
28
28
|
/**
|
|
29
29
|
* A drift signal the orchestrators emit when a per-item payload salvage drops something: a malformed
|
|
30
30
|
* offer product (or a post-purchase data-source product, `product`, named by id and dotted path), an
|
|
31
|
-
* invalid/unknown section stubbed out, an unreadable rule verdict, a shop-config
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* request still succeeds (degraded), so this is the only trace that an upstream
|
|
36
|
-
* on {@link ServerContext}; the host (rebuy-api) wires it to its logger.
|
|
31
|
+
* invalid/unknown section stubbed out, an unreadable rule verdict, a shop-config entry the shared schema
|
|
32
|
+
* inerted (`shopConfig`), unreadable selectable-gift milestones (`giftMilestones` — the widget's gifts are
|
|
33
|
+
* then kept), or a post-purchase product's malformed selected review metafield (`reviews` — the reviews are
|
|
34
|
+
* then omitted; it names the product and the field, never the value). Distinct from a hard
|
|
35
|
+
* {@link UpstreamError} — the request still succeeds (degraded), so this is the only trace that an upstream
|
|
36
|
+
* contract moved. Optional on {@link ServerContext}; the host (rebuy-api) wires it to its logger.
|
|
37
37
|
*/
|
|
38
38
|
export type ServerDrift = {
|
|
39
39
|
detail: string;
|
|
@@ -55,7 +55,10 @@ export declare const isEmptyObject: (x: unknown) => boolean;
|
|
|
55
55
|
export type ServerContext = {
|
|
56
56
|
fetchUpstream: UpstreamFetch;
|
|
57
57
|
host: string;
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Buyer request IP forwarded to the ads engine (the checkout worker can't read it; rebuy-api extracts it).
|
|
60
|
+
* Without it DSPs see one datacenter IP for all traffic — geo/IVT-flagging risk. Monetize only.
|
|
61
|
+
*/
|
|
59
62
|
ipAddress?: string;
|
|
60
63
|
/** Optional sink for {@link ServerDrift} — a degraded-but-successful payload salvage. */
|
|
61
64
|
reportDrift?: (event: ServerDrift) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebuy/rebuy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.58.0",
|
|
4
4
|
"description": "Shared zod schemas, legacy-to-CAB widget transforms, and server orchestrators for Rebuy's Shopify consumers (rebuy-api, admin-nextjs, rebuy-shopify-extensions)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rebuy, Inc.",
|