Gift Card Commerce Apps (Beta)

A gift card Commerce App is a fullstack extension that integrates Salesforce B2C Commerce with an external gift-card processor. The app implements the sfcc.app.giftcard.* hooks and ships Storefront Next components that collect the card and show the applied amount.

Gift cards are a fullstack domain with a platform-defined adapter interface. The platform owns checkout orchestration, Shopper API contracts, amount allocation, and fail-and-reopen. The app owns the processor calls (balance, capture, and refund) and the UI targets those calls surface through. Declare "domain": "giftcard" in commerce-app.json.

Gift Cards Commerce Apps is a beta service that is subject to the Beta Services Terms at Agreements - Salesforce.com or a written Unified Pilot Agreement if executed by Customer, and applicable terms in the Product Terms Directory. Use of this beta service is at the Customer’s sole discretion.

A gift card Commerce App runs only when the feature toggle PaymentFraudOrchestrationEnabled is on. Contact Salesforce Customer Support to enable it.

Note

These hooks run only for an installed Gift Cards Commerce App. Register them from the app cartridge. Native gift certificates (GIFT_CERTIFICATE) continue on their own payment method and never call these hooks.

The payment method ID for Commerce App gift cards is COMMERCE_APP_GIFT_CARD. Payment apps don’t claim it.

Architecture 

A gift card Commerce App has two moving parts:

  1. Server-side hooks in a cartridge. These implement sfcc.app.giftcard.getBalance, sfcc.app.giftcard.capture, and sfcc.app.giftcard.refund.
  2. Extension components in the Storefront Next storefront. These render the apply form, the applied-card line in the order summary, and gift-card management on My Account. Map them in target-config.json. See Gift Card UI Targets.

The shopper enters the card in the storefront extension. The extension calls Shopper Payments for a balance lookup and Shopper Baskets to apply the card. When the order’s payment is processed, the platform captures the gift card before it authorizes any other claimed payment method. If a later step fails, the platform asks the app to refund.

The platform stores only the last four digits of the gift-card number on the payment instrument. Full card number, PIN, and CVC stay off the instrument, payment details, and transaction details.

What Merchants Get 

After a Gift Cards Commerce App is installed and configured on the site:

  • Shoppers can look up a card balance and apply one Commerce App gift card to a basket.
  • The platform allocates how much of that card to use. Shoppers can request a smaller amount. Shoppers can’t request more than the card balance.
  • When the order’s payment is processed, the platform captures the gift card before it authorizes any other claimed payment method, such as a credit card covering the remainder.
  • If capture fails, or a later payment or fraud step fails after a successful capture, the platform fails the order and reopens the basket. The platform then asks the app to refund any captured gift-card funds.

Registration of sfcc.app.giftcard.getBalance controls feature availability. When that hook is absent, balance lookup and apply fail.

Shopper APIs 

Look up balance 

POST /checkout/shopper-payments/v1/organizations/{organizationId}/payment-instruments/balance

Pass paymentMethodId COMMERCE_APP_GIFT_CARD, an optional provider, and providerProperties (card number and any app-specific fields). The platform forwards these to getBalance unchanged. The call doesn’t create a basket or order payment instrument.

Any other paymentMethodId, or a hook that is missing or returns no balance, fails with “Payment instrument balance isn’t implemented.” Apply fails the same way when the hook is missing or returns no balance.

Apply a gift card to a basket 

POST /checkout/shopper-baskets/v1/organizations/{organizationId}/baskets/{basketId}/payment-instruments

1{
2  "paymentMethodId": "COMMERCE_APP_GIFT_CARD",
3  "amount": 25.00,
4  "giftCardRequest": {
5    "brand": "givex",
6    "cardNumber": "4111111111111111",
7    "cvc": "123"
8  }
9}
  • giftCardRequest.brand, giftCardRequest.cardNumber, and giftCardRequest.cvc are required. The platform passes the request to getBalance unchanged.
  • amount is optional. When it is omitted, the platform applies as much of the card as the unpaid remainder allows.
  • When amount is present, it must be greater than or equal to zero and must not exceed the hook balance. The allocated amount is min(shopper amount, unpaid remainder).
  • Unpaid remainder is the basket grand total minus amounts already on other payment instruments.
  • The gift-card currency must match the basket currency.
  • The platform rejects a second gift card on the same basket. Remove the existing instrument before applying another.

Apply calls sfcc.app.giftcard.getBalance. It doesn’t capture funds. On success the basket payment instrument uses method COMMERCE_APP_GIFT_CARD and stores last4 only.

Update a gift-card instrument 

PATCH of a basket gift-card payment instrument can’t change the amount or replace stored credentials. To change the amount or the card, remove the instrument and apply again.

Place the order 

POST /checkout/shopper-orders/v1/organizations/{organizationId}/orders with the basket ID creates the order in CREATED. Gift card capture doesn’t run yet. It runs inside attemptOrderTransition. The payment app’s storefront extension creates the order and then calls the app’s order transition endpoint, which calls OrderMgr.attemptOrderTransition. It does this even when gift cards pay the whole order, so a gift card app depends on an installed payment app to complete checkout. A SCAPI POST or PATCH of a claimed order payment instrument also runs attemptOrderTransition. See Payment Commerce Apps.

Clients must inspect status on HTTP 200. A gift-card capture failure, or a later payment failure after capture, returns HTTP 200 with "status": "failed" from the call that ran attemptOrderTransition. The platform persists the order as failed, releases inventory and coupons, and reopens the basket. The shopper can update that basket and submit again.

Treat HTTP 200 with status: failed as a failed checkout.

Apply validation 

The apply endpoint rejects the following situations:

SituationHTTPTypical detail
Negative shopper amount400The payment instrument amount must not be negative.
Shopper amount greater than card balance400Gift card apply amount exceeds the available card balance.
Gift card already on the basket400Gift card is already applied to this basket. Remove it before applying again.
Missing giftCardRequest, brand, cardNumber, or cvc400Gift card application requires gift card credentials.
Hook balance currency differs from basket currency400Gift card balance currency ‘{balance}’ doesn’t match basket currency ‘{basket}’.
Hook balance is zero or negative400Gift card balance must be greater than zero.
PATCH amount on an existing gift-card instrument400Gift card update doesn’t accept a shopper-provided amount.
PATCH that tries to replace credentials400Gift card update can’t replace stored credentials.
getBalance throws500Checkout cannot proceed with an unknown balance.
Basket grand total is unavailable500Internal server error (apply is aborted).

Checkout Lifecycle 

Gift-card capture is synchronous and runs before card authorization, so a declined gift card never leaves a live card authorization that needs reversing.

1sequenceDiagram
2    participant Shopper
3    participant Platform
4    participant GiftCardApp as Gift Card App
5    participant PaymentApp as Payment App
6    participant FraudApp as Fraud App
7
8    Shopper->>Platform: POST payment-instruments/balance
9    Platform->>GiftCardApp: sfcc.app.giftcard.getBalance
10    GiftCardApp-->>Platform: Money
11    Platform-->>Shopper: balance (no PI created)
12
13    Shopper->>Platform: POST baskets/.../payment-instruments
14    Platform->>GiftCardApp: sfcc.app.giftcard.getBalance
15    GiftCardApp-->>Platform: Money
16    Platform->>Platform: allocate min(optional amount, unpaid remainder)
17    Platform-->>Shopper: basket PI COMMERCE_APP_GIFT_CARD
18
19    Shopper->>Platform: POST /orders
20    opt Fraud app installed
21        Platform->>FraudApp: sfcc.app.fraud.beforePayment
22    end
23    Platform->>Platform: convert basket to CREATED order
24    Platform-->>Shopper: HTTP 200, status created
25
26    Shopper->>Platform: Order transition endpoint (payment app extension), or PATCH order payment instrument
27    Platform->>Platform: attemptOrderTransition
28    Platform->>GiftCardApp: sfcc.app.giftcard.capture
29    alt Capture fails
30        Platform->>Platform: fail order, reopen basket
31        Platform->>GiftCardApp: sfcc.app.giftcard.refund (best effort)
32        Platform-->>Shopper: HTTP 200, status failed
33    else Capture succeeds
34        Platform->>PaymentApp: sfcc.app.payment.onAttemptOrderTransition
35        opt Payment complete and fraud app installed
36            Platform->>FraudApp: sfcc.app.fraud.afterPayment
37        end
38        alt Later payment or fraud fails
39            Platform->>PaymentApp: sfcc.app.payment.reversePayment
40            Platform->>GiftCardApp: sfcc.app.giftcard.refund (best effort)
41            Platform-->>Shopper: HTTP 200, status failed
42        else Success
43            Platform-->>Shopper: HTTP 200, placed order
44        end
45    end

When capture returns an error, the platform skips remaining gift-card captures and fails the order.

Capture and refund each run only when the app registered that hook. A missing capture hook skips capture. A missing refund hook counts as success. The platform skips capture for any gift-card instrument whose transaction is no longer in the created state, so a retry never re-captures. After a capture call, the platform marks the instrument pending.

Gift-card instruments don’t run payment app hooks. Gift cards use the dedicated sfcc.app.giftcard.* hooks.

Fail, Cancel, and Refund 

sfcc.app.giftcard.refund runs inside fail-order and cancel-order, after sfcc.app.payment.reversePayment, for each gift-card instrument.

Refund is best effort. The platform logs a refund error, and fail or cancel continues. The shopper still receives a failed or canceled order. Make refund idempotent so a retry doesn’t double-credit the card. Return Status.ERROR when the processor rejects the refund or the outcome is unknown, so the failed order keeps a record. Return Status.OK only after the processor has refunded.

Extension Points 

Register your hooks. In the app site cartridge, add a package.json that points to hooks.json. Script paths in hooks.json are relative to that file. Each exported function name must match the last segment of its extension point.

1{ "hooks": "./hooks.json" }
1{
2  "hooks": [
3    { "name": "sfcc.app.giftcard.getBalance", "script": "./giftCardHooks.js" },
4    { "name": "sfcc.app.giftcard.capture", "script": "./giftCardHooks.js" },
5    { "name": "sfcc.app.giftcard.refund", "script": "./giftCardHooks.js" }
6  ]
7}

For signatures and the latest class details, see the Script API reference for dw.apps.checkout.hooks.GiftCardHooks.

HookWhenReturnsFailure
sfcc.app.giftcard.getBalanceBalance endpoint and basket applydw.value.MoneyThrow → HTTP 500. An unknown balance is never treated as zero.
sfcc.app.giftcard.captureattemptOrderTransition, before claimed payment onAttemptOrderTransitiondw.system.StatusStatus.ERROR, null, or a throw fails the order and reopens the basket.
sfcc.app.giftcard.refundFail-order and cancel-order, after payment reversedw.system.StatusLogged. Fail or cancel continues. null is treated as success.

sfcc.app.giftcard.getBalance 

getBalance(request). Read-only. Don’t authorize, capture, or persist a payment instrument.

Return a positive available dw.value.Money in the basket currency. The platform rejects zero balances, negative balances, and currency mismatches on apply, but a balance lookup can still return that Money.

The request argument is the Shopper Payments balance body (PaymentInstrumentBalanceRequestWO), including providerProperties. On apply, the platform passes giftCardRequest through to this hook unchanged.

sfcc.app.giftcard.capture 

capture(order, paymentInstrument). Capture the allocated amount on the order payment instrument (the amount the platform stored at apply). Don’t compute a new balance inside capture.

  • Return Status.OK only after the processor has captured, or when the call is safely idempotent for this instrument. Before returning Status.OK, set the payment transaction status to PaymentTransaction.STATUS_CAPTURED.
  • Return Status.ERROR for a declined or failed capture. null is a contract failure and fails the order.
  • Repeated calls for the same order payment instrument must be safe.
  • Capture and refund run with the session switched to the order’s site.

When the processor approves a different amount than the allocated instrument amount, treat that as capture failure. Return Status.ERROR so the platform fails the order and refunds. Don’t accept a partial capture.

sfcc.app.giftcard.refund 

refund(order, paymentInstrument). Refund a settled capture when the order is failed or canceled after funds were taken.

  • Make repeated calls for the same instrument safe.
  • Prefer returning Status.OK or Status.ERROR. The platform logs a throw and continues the fail or cancel.

Data Model 

Write processor outcome onto the payment transaction before returning an error status, so the failed order keeps a durable record.

1var GiftCardTransactionDetails = require('dw/extensions/payments/GiftCardTransactionDetails');
2var PaymentTransaction = require('dw/order/PaymentTransaction');
3var Status = require('dw/system/Status');
4
5exports.capture = function (order, paymentInstrument) {
6    var txn = paymentInstrument.getPaymentTransaction();
7    var result = ExampleGiftCard.capture(txn.getAmount());
8
9    var details = new GiftCardTransactionDetails();
10    if (result.approved && result.amountMatchesAllocation) {
11        details.setApprovalCode(result.approvalCode);
12        txn.setTransactionDetails(details);
13        txn.setStatus(PaymentTransaction.STATUS_CAPTURED);
14        return new Status(Status.OK);
15    }
16
17    details.setReasonCode(GiftCardTransactionDetails.REASON_CODE_DECLINED);
18    details.setProcessorResponseText(result.processorText);
19    txn.setTransactionDetails(details);
20    return new Status(Status.ERROR);
21};

GiftCardTransactionDetails 

Set it through paymentInstrument.getPaymentTransaction().setTransactionDetails(...). Each payment transaction holds at most one GiftCardTransactionDetails object. A later refund replaces prior capture values.

FieldUse
approvalCodeSuccessful capture or refund. Leave unset on errors.
reasonCodeErrors only. Constants: REASON_CODE_DECLINED (DECLINED), REASON_CODE_INSUFFICIENT_FUNDS (INSUFFICIENT_FUNDS).
processorResponseTextHuman-readable processor text. Language is provider-defined.

You may also set paymentTransaction.setStatus(...) (for example STATUS_CAPTURED) to match processor state.

GiftCardPaymentDetails 

Instrument details expose last4 only (dw.extensions.payments.GiftCardPaymentDetails). Don’t store the full card number, PIN, or CVC on payment details, transaction details, custom attributes, or logs.

Interaction With Payment and Fraud Apps 

Gift-card instruments are separate from claimed payment methods. Payment apps don’t claim COMMERCE_APP_GIFT_CARD.

Mixed tender is one gift card plus one claimed method (typically a credit card) for any remainder. Capture still runs first. When the later tender fails, gift-card refund runs as part of fail-order, after reversePayment.

When a fraud app is installed, beforePayment runs on the basket before the order exists, and afterPayment runs after gift-card capture and claimed-payment authorization are both complete. A post-authorization NOT_GUARANTEED fails the order and refunds the captured gift card. See Fraud Commerce Apps and Payment Commerce Apps.

Gift Card UI Targets 

Storefront Next exposes named extension slots for the apply form, the applied amount, and My Account gift-card management. A gift card Commerce App registers React components against these targets. The hooks alone don’t render a shopper-facing card form. See Building Your Commerce App for how target-config.json maps components to target IDs.

SurfaceTarget IDWhat the Component Does
Cart order summarysfcc.cart.giftCards.applyCollect brand, card number, and CVC, and then call the balance and apply Shopper APIs.
Order summarysfcc.orderSummary.giftCards.appliedShow the masked card (last4) and the allocated amount.
My Account, payment methodssfcc.myAccountPaymentMethods.giftCards.manageGift-card management on the saved-payment-methods page.

Storefront checklist 

  • Branch on order status for every HTTP 200 from a call that can run attemptOrderTransition.
  • After status: failed, reuse the reopened basket. The basket ID remains valid.
  • Show apply errors from the HTTP 400 detail (duplicate card, negative amount, amount above balance, missing credentials, currency mismatch, zero balance).
  • Remove and re-apply to change the amount. PATCH rejects a shopper-provided amount and credential replacement.
  • Keep full gift-card numbers out of client traces and logs.
  • Leave native GIFT_CERTIFICATE checkout on its own payment method.

Beta Limitations 

  • A basket supports one Commerce App gift card.
  • The platform allocates the gift-card amount at apply time. When basket totals change after apply, remove and re-apply the card so the allocation matches the new unpaid remainder.
  • Refund after fail or cancel is best effort at the platform layer. The app owns idempotent processor refunds and operations tooling for any refund the processor rejects.
  • Custom merchant cartridges must leave these extension points unregistered.

See Also