hydrogen icon indicating copy to clipboard operation
hydrogen copied to clipboard

[2025-10] Add cartGiftCardCodesAdd mutation and remove Update filtering

Open juanpprieto opened this issue 2 months ago • 1 comments

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

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

Checklist

  • [x] I've read the Contributing Guidelines
  • [x] I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • [x] I've added a changeset if this PR contains user-facing or noteworthy changes
  • [x] I've added tests to cover my changes
  • [x] 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%)

juanpprieto avatar Oct 23 '25 19:10 juanpprieto