Skip to content

Conversation

@juanpprieto
Copy link
Contributor

@juanpprieto juanpprieto commented Oct 23, 2025

WHY are these changes introduced?

Fixes #3271

Storefront API 2025-10 added cartGiftCardCodesAdd mutation for appending gift card codes without replacing existing ones. Hydrogen only implemented cartGiftCardCodesRemove in PR #3128, leaving users without a way to add codes incrementally. Since the API only returns the last 4 digits of applied gift cards (security constraint), users cannot fetch existing codes to preserve them when using the Update mutation.

This PR also removes legacy duplicate filtering from cartGiftCardCodesUpdate to align with API 2025-10 thin wrapper architecture.

WHAT is this pull request doing?

New Feature: cartGiftCardCodesAdd

Adds thin wrapper for cartGiftCardCodesAdd mutation following the established Add mutation pattern.

Files created:

  • cartGiftCardCodesAddDefault.ts - Core implementation (no duplicate filtering)
  • cartGiftCardCodesAddDefault.test.ts - 7 comprehensive tests
  • cartGiftCardCodesAddDefault.doc.ts - Documentation metadata
  • cartGiftCardCodesAddDefault.example.js/ts - Usage examples

Integration:

  • Exported from createCartHandler as addGiftCardCodes
  • Added CartForm.ACTIONS.GiftCardCodesAdd action type
  • Exported from package index

Usage:

// Using createCartHandler
const cart = createCartHandler({storefront, getCartId, setCartId});
await cart.addGiftCardCodes(['SUMMER2025', 'WELCOME10']);

// Using CartForm
<CartForm 
  action={CartForm.ACTIONS.GiftCardCodesAdd} 
  inputs={{giftCardCodes: ['SUMMER2025']}}
>
  <button>Apply Gift Card</button>
</CartForm>

Breaking Change: cartGiftCardCodesUpdate

Removed client-side duplicate code filtering to align with thin wrapper pattern.

Before:

// Hydrogen filtered unique codes before API call
const uniqueCodes = giftCardCodes.filter((value, index, array) => 
  array.indexOf(value) === index
);
// Only unique codes sent to API

After:

// Codes pass directly to API
const {cartGiftCardCodesUpdate, errors} = await storefront.mutate(
  MUTATION, 
  { variables: { giftCardCodes } }
);

Architecture Decision:

Mutation Type Filtering Count Pattern
Add mutations None 3/3 (100%) Thin wrapper
Remove mutations None 3/3 (100%) Thin wrapper
Update mutations Changed 1/3 (33%) Now thin wrapper

Migration: If you need client-side deduplication:

const uniqueCodes = codes.filter((v, i, a) => a.indexOf(v) === i);
await cart.updateGiftCardCodes(uniqueCodes);

HOW to test your changes?

🎩 Top Hat

Prerequisites

  • Hydrogen project on 2025-10 API version
  • Storefront with gift card products enabled
  • Test gift card codes ready

Testing Steps

Feature 1: Add Gift Card Codes

  1. Setup test environment:
npm create @shopify/hydrogen@latest
cd your-project
# Ensure API version is 2025-10 in .env
  1. Create test route (app/routes/test-gift-cards.tsx):
import {CartForm} from '@shopify/hydrogen';

export default function TestGiftCards() {
  return (
    <div>
      <h1>Test Gift Card Add</h1>
      
      {/* Test 1: Add single code */}
      <CartForm 
        action={CartForm.ACTIONS.GiftCardCodesAdd}
        inputs={{giftCardCodes: ['TESTCODE1']}}
      >
        <button>Add Single Code</button>
      </CartForm>
      
      {/* Test 2: Add multiple codes */}
      <CartForm 
        action={CartForm.ACTIONS.GiftCardCodesAdd}
        inputs={{giftCardCodes: ['CODE1', 'CODE2']}}
      >
        <button>Add Multiple Codes</button>
      </CartForm>
      
      {/* Test 3: Add duplicate codes (should not filter) */}
      <CartForm 
        action={CartForm.ACTIONS.GiftCardCodesAdd}
        inputs={{giftCardCodes: ['DUP', 'DUP', 'UNIQUE']}}
      >
        <button>Add with Duplicates</button>
      </CartForm>
    </div>
  );
}
  1. Test with createCartHandler (app/routes/api.gift-cards.tsx):
import {createCartHandler} from '@shopify/hydrogen';

export async function action({context}) {
  const cart = createCartHandler({
    storefront: context.storefront,
    getCartId: context.session.get('cartId'),
    setCartId: (cartId) => context.session.set('cartId', cartId),
  });
  
  // Test: Add codes without replacing existing
  const result = await cart.addGiftCardCodes(['SUMMER2025', 'WELCOME10']);
  
  return json(result);
}
  1. Expected behavior:
    • Codes append to existing gift cards
    • Existing codes remain in cart
    • API handles any duplicate normalization
    • No client-side filtering

Feature 2: Update No Longer Filters

  1. Test duplicate handling:
// Duplicates now pass to API
await cart.updateGiftCardCodes(['CODE1', 'CODE1', 'CODE2']);
// Previously would filter to ['CODE1', 'CODE2']
// Now passes ['CODE1', 'CODE1', 'CODE2'] to API
  1. Expected behavior:
    • API receives all codes including duplicates
    • API handles case-insensitive normalization
    • No console errors
    • Cart updates successfully

Edge Cases to Test

  • Empty array to Add (should be no-op)
  • Very long code strings (>50 chars)
  • Special characters in codes
  • Case variations (GIFT123 vs gift123)
  • Existing + new codes (verify append)
  • Invalid gift card code (API should return error)

Validation Checklist

  • All tests pass locally (447 tests)
  • TypeScript clean (no errors)
  • Lint passes
  • Changeset created
  • Documentation added (doc.ts, examples)
  • Investigation documented (investigation-3271.md)

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

Summary of Changes

New:

  • cart.addGiftCardCodes(codes) - Append codes without replacing
  • CartForm.ACTIONS.GiftCardCodesAdd - Form action

Breaking:

  • cart.updateGiftCardCodes(codes) - No longer filters duplicates client-side

Files: 13 files, +400/-9 lines
Tests: 7 new tests, all passing
Architecture: 100% thin wrapper consistency (was 78%)

Tests verify requirements for new Add mutation:
- Add single gift card code
- Add multiple codes in one call
- Handle empty array
- Override cartFragment
- Mutation includes userErrors, warnings, @incontext
- NO duplicate filtering (thin wrapper pattern)

Test Results: FAILING (expected)
Error: Failed to resolve import "./cartGiftCardCodesAddDefault"
Reason: Module doesn't exist yet (TDD RED phase)

Tests include validation that implementation does NOT contain:
- unique filtering
- filter() calls
- indexOf() duplicate checking

This follows thin wrapper pattern per investigation-3271.md
findings that Add mutations should delegate to API.

Related: #3271
Implementation:
- Thin wrapper pattern (no duplicate filtering per investigation)
- Follows cartGiftCardCodesRemove structure
- JSDoc explains append vs replace semantics
- Uses giftCardCodes (strings) not IDs

Architectural decision:
- NO duplicate filtering (API handles case-insensitive normalization)
- Consistent with cartLinesAdd, cartDeliveryAddressesAdd patterns
- All Add mutations are thin wrappers (0/3 filter)
- Documented in investigation-3271.md

Test Results: All 7 tests passing ✅
- Basic functionality (single/multiple codes)
- Empty array handling
- CartFragment override
- Mutation structure validation
- Duplicate codes pass through (no filtering)

TypeScript: ✅ Clean

Related: #3271
Tests verify that:
- Duplicate codes pass through to API without filtering
- Case-insensitive codes handled by API (GIFT123 vs gift123)
- Aligns with API 2025-10 behavior (case-insensitive normalization)

Related: investigation-3271.md E-016, E-017, E-019
Breaking change for API 2025-10:
- Removes client-side unique code filtering
- Passes codes directly to Storefront API
- API handles case-insensitive normalization
- Consistent with Add/Remove mutations (thin wrapper pattern)
- Updated JSDoc to document behavior change

Evidence:
- API schema describes codes as 'case-insensitive'
- No DUPLICATE_GIFT_CARD error exists in CartErrorCode/CartWarningCode
- Filtering was copy-paste from discount codes (E-016)
- All Add/Remove mutations delegate to API without filtering

Closes part of #3271
Complete integration of addGiftCardCodes:
- Added CartForm action type GiftCardCodesAdd
- Exported addGiftCardCodes from createCartHandler
- Added doc file with related mutations
- Added JS/TS example files
- Exported function from package index
- Updated all test assertions

Tests: ✅ All 447 passing
TypeScript: ✅ Clean

Related: #3271
@juanpprieto juanpprieto requested a review from a team as a code owner October 23, 2025 19:15
@shopify
Copy link
Contributor

shopify bot commented Oct 23, 2025

Oxygen deployed a preview of your feat-3271-gift-card-codes-add branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment October 23, 2025 7:35 PM
custom-cart-method ✅ Successful (Logs) Preview deployment Inspect deployment October 23, 2025 7:36 PM
sitemap ✅ Successful (Logs) Preview deployment Inspect deployment October 23, 2025 7:36 PM
metaobjects ✅ Successful (Logs) Preview deployment Inspect deployment October 23, 2025 7:36 PM
third-party-queries-caching ✅ Successful (Logs) Preview deployment Inspect deployment October 23, 2025 7:36 PM

Learn more about Hydrogen's GitHub integration.

@juanpprieto juanpprieto changed the title Add cartGiftCardCodesAdd mutation and remove Update filtering [2025-10] Add cartGiftCardCodesAdd mutation and remove Update filtering Oct 23, 2025
Changes:
- Added AddGiftCardForm component (uses GiftCardCodesAdd action)
- Updated cart.tsx to handle GiftCardCodesAdd action
- Changed gift card input form to use Add instead of Update
- Keeps UpdateGiftCardForm for backward compatibility

Users can now add gift cards without replacing existing ones in skeleton.

Related: #3271
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant