41 "note": "Live verification established that the source plugin has no REST namespace of its own. Issuance data rides as line_item meta_data on /wc/v3/orders once an order reaches processing or completed; redemption and balance data have no REST or order-meta surface and require the profile's gift-card-activity entity."
42 }
43 ],
44 "preferredWrite": {
45 "surface": "Gift Cards",
46 "endpoint": "POST /gift-cards/v1/gift-cards",
47 "writerId": "createGiftCard",
48 "verification": "verified-live",
49 "importSafe": true,
50 "bulk": false
51 },
52 "fallbacks": [
53 {
54 "classification": "cms",
55 "when": "Only for redemption/transaction history, which has no public write surface, or for source-only fields such as the original card design."
56 }
57 ],
58 "reliability": {
59 "status": "partially-reliable",
60 "flags": []
61 },
62 "pitfalls": [
63 {
64 "code": "suppress-recipient-email-on-import",
65 "severity": "info",
66 "summary": "Omit `giftCard.notificationInfo` entirely on import. Including it emails the recipient — immediately when `notificationDate` is empty — so a historical import would re-mail every card holder. Covered by site mute: when getSiteMuteState reads muted:true, this is not a reason to block, skip, delay or warn about the entity. Omitting notificationInfo is still the simplest payload."
67 },
68 {
69 "code": "code-length-8-to-20",
70 "severity": "blocker",
71 "summary": "`giftCard.code` is constrained to 8-20 characters and is immutable. Source codes outside that range cannot be preserved, which breaks the code customers already hold; surface affected cards before import rather than silently rewriting them."
72 },
73 {
74 "code": "code-alphanumeric-only",
75 "severity": "blocker",
76 "summary": "Verified live: `giftCard.code` rejects any non-alphanumeric character. A hyphenated code returns `428 INVALID_CODE`, independent of the 8-20 length check above. Sources whose default format includes separators therefore cannot preserve those codes as-is. Stripping non-alphanumeric characters produces an acceptable code, but changes what the customer already holds; report affected cards and require an explicit decision rather than stripping silently."
77 },
78 {
79 "code": "full-code-returned-only-at-create",
80 "severity": "blocker",
81 "summary": "The create response is the only place the full unobfuscated code appears; Get/Query return it masked (`****-****-****-4444`) plus `codeSuffix`. Persist the code-to-id crosswalk in the same step as the create or it is unrecoverable. Observed 2026-10-27 with a site-owner token: Get and Query also returned a `fullCode` field. That contradicts the docs, so do not rely on it; keep the code from your own create request."
82 },
83 {
84 "code": "balance-is-read-only",
85 "severity": "warning",
86 "summary": "Only `initialValue` is settable; `balance` is derived from transactions and there is no public adjust-balance method. A partly-spent card must either be created at its remaining value (loses face value and spend history) or created at face value and reduced via a redeem call (preserves both, adds a synthetic transaction). Use one balance policy per run (Policy A/B, never both for the same card) and lib/gift-card-balance-reconciliation.js's buildBalanceReconciliationReport() (fixed 2026-08-18) for the reporting-only drift check this implies — it never proposes a balance patch, only disable-and-replace or investigate, for exactly the no-adjust-balance-method reason stated here."
87 },
88 {
89 "code": "no-transaction-history-import",
90 "severity": "warning",
91 "summary": "No public method writes historical redemption transactions, so per-card spend history is not reproducible natively."
92 },
93 {
94 "code": "idempotency-key-is-not-a-replay",
95 "severity": "warning",
96 "summary": "VERIFIED LIVE 2026-10-27: `idempotencyKey` is a request parameter beside `giftCard`, not a card field (a key nested in `giftCard` is accepted and ignored). Repeating a create with the same key does NOT return the first answer: it fails `409 ALREADY_EXISTS` with a uniqueConstraintViolation on `code`, as any repeated code does. Reconcile a retry or unknown outcome by Query Gift Cards with `filter: { code: <full code> }` and compare amount, currency and expiry; createGiftCard does this."
97 }
98 ],
99 "mappingGuidance": [
100 "Always send `source: \"MANUAL\"` — it is required, and it distinguishes imported cards from cards bought through a Wix order.",
101 "`idempotencyKey` is a request parameter beside `giftCard` (createGiftCard lifts a nested key). The card's unique code, not the key, is what prevents a second card: see idempotency-key-is-not-a-replay.",
102 "`expirationDate` is accepted at create, so original expiry dates are preserved.",
103 "Decide the partial-balance policy in the mapping plan and surface it in the execution plan; do not default silently.",
104 "Amounts are decimal strings with at most 2 decimal places, and currency is ISO-4217 alphabetic.",
105 "CORRECTED 2026-08-17 (VERIFIED LIVE): `currency` is a TOP-LEVEL field on the giftCard object, NOT nested inside `initialValue`. `{ giftCard: { initialValue: { amount, currency } } }` fails with `400 currency must not be empty`; the working shape is `{ giftCard: { initialValue: { amount }, currency, source, code }, idempotencyKey }`.",
106 "Write cards with createGiftCard. It never sends `notificationInfo` (an import copies cards, it does not sell them) and returns created | adopted | conflict | failed."
128 "path": "searched: gift card, stored value, voucher, store credit — across business-solutions/gift-cards and e-commerce/payments/gift-cards (2026-08-01)"
129 },
130 {
131 "type": "live-verification",
132 "path": "anonymized-live-verification",
133 "note": "Verified live after installing the Wix Gift Cards app: create and read-back succeeded through rp-target-wix/lib/gift-card-build.js and established the currency-placement and code-alphanumeric constraints. Not exercised: Send Gift Card Email, Query/Search, checkout redemption, or disabling/deleting a card."
134 },
135 {
136 "type": "live-run",
137 "path": "anonymized-live-verification",
138 "note": "Verified live on a disposable site: same code with the same top-level idempotencyKey → 409 ALREADY_EXISTS on code (no replay); same code with another amount → same 409; nested idempotencyKey accepted and ignored; Query Gift Cards with filter { code: <full code> } returned exactly that card and [] for an unknown code; Get returned masked code, codeSuffix and fullCode."