Add ach payment method and restructure delivery method logic #1313
Add ach payment method and restructure delivery method logic #1313pbennett1-godaddy wants to merge 30 commits into
Conversation
…-ach-paymentMethod # Conflicts: # examples/nextjs/app/page.tsx # packages/localizations/src/deDe.ts # packages/localizations/src/enIe.ts # packages/localizations/src/enUs.ts # packages/localizations/src/esAr.ts # packages/localizations/src/esCl.ts # packages/localizations/src/esCo.ts # packages/localizations/src/esEs.ts # packages/localizations/src/esMx.ts # packages/localizations/src/esPe.ts # packages/localizations/src/esUs.ts # packages/localizations/src/frCa.ts # packages/localizations/src/frFr.ts # packages/localizations/src/idId.ts # packages/localizations/src/itIt.ts # packages/localizations/src/ptBr.ts # packages/localizations/src/qaPs.ts # packages/localizations/src/trTr.ts # packages/localizations/src/viVn.ts # packages/localizations/src/zhCn.ts # packages/localizations/src/zhSg.ts # packages/react/src/components/checkout/payment/lazy-payment-loader.tsx # packages/react/src/lib/godaddy/checkout-mutations.ts # packages/react/src/lib/godaddy/checkout-queries.ts
…-ach-paymentMethod # Conflicts: # packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx
…-ach-paymentMethod # Conflicts: # packages/localizations/src/deDe.ts # packages/react/src/components/checkout/payment/lazy-payment-loader.tsx # packages/react/src/components/checkout/payment/utils/conditional-providers.tsx
…-ach-paymentMethod
🦋 Changeset detectedLatest commit: 7c63c31 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
'This PR is stale as it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' |
|
'This PR was closed because it has been stalled for 5 days with no activity.' |
# Conflicts: # packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx # packages/react/src/components/checkout/payment/checkout-buttons/paze/godaddy.tsx # packages/react/src/components/checkout/pickup/local-pickup.tsx
|
I reviewed the PR and found a few issues worth addressing before merge.
Process note: the PR includes generated package/changelog version bumps while also keeping a changeset. Unless this is intentionally a release PR, I would remove generated package/changelog versioning and let Changesets handle the release bump on merge. |
1. Tips not submitted/charged — can't address in this PRYou're right that the displayed total includes the tip while the confirm payload doesn't carry it, but MutationConfirmCheckoutSessionInput has no tip field today (only enableTips exists in the schema 2. PURCHASE fallback overwritten by DeliveryMethodForm — working as intendedThis is by design. DeliveryMethodForm only mounts when enableShipping || enableLocalPickup, and availableMethods is built strictly from those session flags. The PURCHASE fallback in For your specific example (enableShipping=false, enableLocalPickup=true, items SHIP-only): the merchant has enabled pickup at the session level, so resolving to PICKUP is the intended outcome. The 3. Collapsed mobile summary omits tip — fixed ✅Confirmed the bug: mobile accordion header used totals?.total?.value directly while the expanded DraftOrderTotals uses total + tip. Updated the header in checkout-form.tsx to use orderTotal + tipTotal 4. GoDaddy CC/ACH selectable without app id — fixed ✅Good catch on the predicate split. Unified everything around getApplicationId(session, godaddyPaymentsConfig?.appId):
Same predicate now drives selector inclusion, provider wrapping, and renderer fallback. 5. Standard GDP wallets confirming as cardExpects card for all of these and caterogizes on the gopay side. |
Summary
Adds ACH (bank account) as a new payment method for GoDaddy Payments, surfaces tips, fees, shipping, and taxes correctly in the order summary, rewrites the custom tip input for better
multi-currency UX, hardens billing & delivery-method resolution for no-fulfillment / all-NONE / shipping-disabled orders, and ships several checkout bug fixes (inline billing for ACH & pickup,
stuck nonce loading state, $0 line items, duplicate DOM IDs).
Packages affected:
@godaddy/react(patch),@godaddy/localizations(patch)🆕 ACH Payment Method
Adds full support for ACH (bank account) payments via GoDaddy Payments:
GoDaddyACHForm— Collect iframe for bank account input (account holder name/type, routing number, account number) with custom CSS matching the checkout themeACHCheckoutButton— submit button that triggers nonce collection and form validationPoyntACHCollectProvider— dedicated React context for the ACH collect instance and nonce loading state, separate from the credit card collect providerlazyPaymentComponentRegistrywith lazy-loaded form and button componentsLandmarkicon and label/description resolution for the ACH tabConditionalPaymentProviderswraps children in the ACH provider only when GoDaddy Payments is configured andsession.paymentMethods.ach.processor === 'godaddy'LazyPaymentMethodRendererreturnsnullfor the ACH method unless GoDaddy Payments has anappIdand the session listsachwith thegodaddyprocessor (and similarlyhides the GoDaddy CC form when no
appIdis configured)CreateCheckoutSessionMutation,GetCheckoutSessionQuery, and the introspection schema to includeach { processor, checkoutTypes }PaymentMethodType.ACH = 'ach'achandexpresspayment method config🧾 Inline Billing for ACH & Pickup Fix for Credit Card
Refactored billing-address logic in
payment-form.tsxandpayment-methods/credit-card/container.tsx:INLINE_BILLING_PAYMENT_METHODSlist (card,ach) — these collect billing inline, so the standalone billing block / address toggle is suppressed for bothPaymentAddressTogglenow only renders for non-inline-billing methodsshouldShowBillingNamesOnlynow correctly evaluates(!useShippingAddress || isPickup)so the names-only billing block appears when neededGoDaddyACHFormmirrors the same logic, including names-only fallback whenenableBillingAddressCollection === false🚚 Billing & Delivery-Method Resolution for No-Fulfillment Orders
Fixes several related defects that surfaced when an order had no actionable shipping/pickup fulfillment. This includes:
fulfillmentMode: NONE(with shipping/pickup either enabled or not at the session level) —deliveryMethodpreviously fell through toPURCHASEbut downstream billinglogic didn't recognize that state, so the full billing-address form rendered even with
enableBillingAddressCollection: false.enableShipping: falsewhile line items still declareSHIP— the shipping form wasn't rendered (correctly), but the schema still required shipping fields and billing rendered asif shipping were available, leaving the form unsubmittable.
enableShipping: falseANDenableLocalPickup: false(purchase-only checkout) regardless of what fulfillment modes the line items declare — the order is payment-only and costsare assumed hardcoded on the order; no dynamic shipping or pickup logic should run, but
mapOrderToFormValueswas still resolvingdeliveryMethodfrom items and downstream code mis-treated it as ashipping flow.
enableShipping: true,enableLocalPickup: false, items have only PICKUP fulfillment) — the form would resolve toPICKUPeven though the session disablesit, with no UI path to correct it.
In all of these the full billing-address form was being rendered even with
enableBillingAddressCollection: false, schema validation could either over- or under-validate billing/shipping fields, andthe form could land in an unrecoverable state for contradictory item-vs-session configurations.
mapOrderToFormValuesis now session-aware. AcceptsenableShipping/enableLocalPickupand canonicalizesdeliveryMethodagainst session capabilities, with an explicitisPurchaseMode = enableShipping === false && enableLocalPickup === falseshort-circuit toPURCHASE(skips line-item scans entirely — line items withSHIP/PICKUP/NONE/mixed fulfillment all resolve toPURCHASEwhen the session is payment-only). Item-level fulfillment is also gated per-flag so asymmetric configs (e.g. SHIP items with
enableShipping: falsebutenableLocalPickup: true) fall back correctlyto
PICKUP/PURCHASEinstead of producing an unreachableSHIPstate.Callers updated to pass the new flags:
checkout-form-container.tsxandpayment/checkout-buttons/express/godaddy.tsx.Unified
billingIsSeparateFromShippingpredicate (!isShipping || !useShippingAddress) replaces the buggy(!useShippingAddress || isPickup)gate in:payment/payment-methods/credit-card/container.tsxpayment/payment-methods/ach/godaddy.tsxpayment/payment-form.tsxcheckout.tsxschemasuperRefineResult:
enableBillingAddressCollection: falsecorrectly renders names-only (and validates only first/last name) instead of falling through to the full address form when there's no shippingaddress to copy from.
Schema
requireShippingAddressgated onenableShippingas defense-in-depth so contradictory configs (SHIP items +enableShipping: false) don't block submission with errors for fields theform refuses to render.
Field-filter validation in
custom-form-provider.tsxchanged frompaymentUseShippingAddress && !isPickuptopaymentUseShippingAddress && isShipping, so billing field errors actuallysurface in
PURCHASE/ no-shipping flows instead of being silently stripped from the validation pass.Removed dead
_isPickuplocals in CreditCardContainer and ACH godaddy form after the refactor.Net behavior for a payment-only checkout (
enableShipping: false,enableLocalPickup: false,enableBillingAddressCollection: false, line items with any mix of fulfillment modes includingall-NONE):
deliveryMethodresolves toPURCHASE<DeliveryMethodForm />is not mounted (its parent gates onenableShipping || enableLocalPickup)💰 Order Summary: Tips, Fees, Shipping, Taxes
total + tip) so customers see the true amount they'll be chargedFeesline inDraftOrderTotals(with skeleton +checkout.summary.totals.fees.beforeextension target). Wired throughfeeTotalfromuseDraftOrderTotalsandan
update-draft-order-feesmutation-key loading stateshowShippingLine,showTaxesLine,showFeesLineinCheckoutForm. Merchants who disable collection but pre-applya value still see it reflected in the summary:
(isShipping && enableShipping) || shipping > 0enableTaxCollection || taxTotal > 0feeTotal > 0shippingis now read directly fromtotals.shippingTotal.valueinstead of summingorder.shippingLines(removes the unuseduseDraftOrdercall)✏️ Custom Tip Input Rewrite
Replaced
<input type="number">with a polished currency input using the "format on blur" pattern (same UX as Stripe, Square, Shopify):"10."or""are preserved for natural editing"10.5"→"10.50")@tanstack/react-pacer'suseDebouncedValue)$vs suffix€)convertMajorToMinorUnitshardened: now returns0forNaN, negative, or otherwise invalid inputgrid-cols-1 sm:grid-cols-3/grid-cols-1 sm:grid-cols-2)🐛 Bug Fixes
Pay Nowbuttons now flipisLoadingNoncetotrueonly after validation succeeds, and reset it correctly onerror, on failedvalidatedevents, and inside the
confirmCheckoutcatchblock. The button is also short-circuited when already disabled or whencollectisn't ready$0line items: changeditem.originalPrice && …→item.originalPrice != null && …so items with a0original price correctly render the price column instead of being falsy-skippeduseId()(gdpay-card-element-…,gdpay-ach-element-…), preventing collisions whenmultiple checkouts mount on the same page
applicationIddeps: ApplePay, GooglePay, Paze, Express, credit card, and ACH forms now computeapplicationIdonce at the component level and include it in theiruseEffectdepsinstead of re-deriving inline (previously caused subtle re-mount churn)
🌐 Localizations
achpayment method name ("Bank Account"/ localized equivalents) and description across all 20 localesfeestotals label across all localesAUTHORIZATION_FAILEDerror message ("Failed to authorize payment"/ localized equivalents) across all 20 localesChangeset
Test Plan