1'use strict';23// An imported order carries its buyer's member id, resolved from the member crosswalk4// and corroborated, or it carries nothing.5//6// `buyerInfo.memberId` is what member-facing reads authorize on: a member-identity Get Order7// returns an order only if that member placed it, and the invoice download backend checks the8// same field. So an imported order with no memberId is invisible to its own buyer (dormant, no9// leak), and one with the WRONG memberId hands that member someone else's order and invoice, with10// both checks agreeing. Import Order stores whatever GUID it is given (verified live 2026-09-05:11// a memberId that is not a member is accepted and read back as-is). Nothing downstream catches a12// bad link, so this module fails CLOSED: a member id is set only when the crosswalk names it AND13// the member's login email is the order's billing email.
68 // A number above 2^53 has ALREADY been rounded by whoever parsed it; it cannot name a customer.
69 if (typeof sourceCustomerId === 'number' && !Number.isSafeInteger(sourceCustomerId)) throw new Error(`sourceCustomerId ${sourceCustomerId} is not a safe integer; pass large ids as strings`);
71 if (!/^\d+(\.0+)?$/.test(raw)) throw new Error(`sourceCustomerId must be a non-negative integer (WooCommerce customer id), got ${JSON.stringify(sourceCustomerId)}`);
72 // Normalized as a STRING, never through Number(): above 2^53 that rounds, and review
73 // demonstrated "9007199254740993" keyed as ...992 -- another customer's row, and so another
74 // customer's order and invoice handed to the wrong member.
101 if (!customerId) return finish(contactId ? OUTCOMES.CONTACT_ONLY : OUTCOMES.GUEST, null);
102 if (!memberCrosswalk || typeof memberCrosswalk.get !== 'function') throw new Error('resolveOrderBuyer needs a memberCrosswalk with get(sourceCustomerId) when the order has a customer id');
142// The buyerInfo to put on the Import Order body, from a STAMPED resolver result. Refuses anything
143// else, so a hand-built { memberId } cannot reach the order.
144function buyerInfoFrom(buyer) {
145 if (!verifyBuyerStamp(buyer)) throw new Error('buyerInfo must come from resolveOrderBuyer (its stamp did not verify); a hand-built memberId is exactly the wrong-attribution risk this exists to prevent');
146 const info = {};
147 if (buyer.contactId) info.contactId = buyer.contactId;
148 if (buyer.memberId) info.memberId = buyer.memberId;
149 return info;
150}
151
152// The only keys a mapped order body may carry under buyerInfo BEFORE the composite sets the
153// buyer: an email, and a contactId the contact import resolved. Everything else -- memberId under
154// any spelling, visitorId, snake_case siblings -- is refused, because Wix's proto-JSON accepts
155// `member_id` as readily as `memberId` and a refusal that checks one spelling checks nothing.
185 // BUG FIX (2026-09-27): the guard used to test `/buyer/i`
186 // against every top-level key, which also matched `buyerNote` and threw for 100% of orders,
187 // independent of any real data problem -- the root cause of every "order/all: 0 landed" run
188 // while it stood. The fix carves out only that one known-legitimate field by exact normalized name,
189 // rather than narrowing to an exact `buyerInfo` match -- a narrower check would have let a
190 // smuggled top-level `buyerId`/`buyerMemberId`/bare `buyer` scalar straight through with
191 // nothing else in this module (or the Import Order schema) refusing it. Any OTHER key that
192 // merely contains "buyer" -- the container under a variant spelling, or a scalar identity
193 // field under any name -- is refused; the buyer goes through `buyer` from resolveOrderBuyer
194 // and nowhere else.
195 if (ALLOWED_BUYER_ADJACENT_KEYS.has(normalized)) continue;
196 throw new Error(`order.${key} is not accepted; the buyer goes through \`buyer\` from resolveOrderBuyer`);
197 }
198 const info = order && order.buyerInfo;
199 if (info === undefined || info === null) return;
200 if (typeof info !== 'object' || Array.isArray(info)) throw new Error('order.buyerInfo must be an object');
201 const bad = Object.keys(info).filter((k) => !BODY_BUYER_KEYS.includes(k));
202 if (bad.length > 0) throw new Error(`order.buyerInfo may only carry ${BODY_BUYER_KEYS.join(', ')} before the composite sets the buyer; got ${bad.join(', ')} -- a member id is never the caller's to set`);