Skip to content

@W-20508471: Implement PWA Adyen integration for Express#3640

Merged
amittapalli merged 7 commits intot/team404/sfp-on-pwafrom
amittapalli/pwa-adyen-updates
Feb 9, 2026
Merged

@W-20508471: Implement PWA Adyen integration for Express#3640
amittapalli merged 7 commits intot/team404/sfp-on-pwafrom
amittapalli/pwa-adyen-updates

Conversation

@amittapalli
Copy link
Contributor

@amittapalli amittapalli commented Feb 5, 2026

Description

  • Updated commerce-sdk-react with the latest isomorphic changes so we know we are testing newer updates. Note: some of these updates have already been pushed to develop branch and we can rebase it again once we pull from develop

  • Update Express Buttons component to process Payments for Adyen as well. Adyen does not confirm the payment client-side via the SDK. It only calls createPaymentIntent action

  • In addition Adyen does not call the pre approve action as Stripe does. So, changes include ensuring that the shipping, billing address, and basket creation happen from the createPaymentIntent itself just for Adyen.

  • Some of the Adyen updates caused some unnecessarily remounting of the component, which broke the payment process. So added some extra guards around that

  • Also addressed the Fail Order itself failing scenario. This can happen if a paymentError is dispatched because createPaymentIntent did not receive a response OR if the patch payment instrument on order API fails and fail order has to be called. Sometimes a race condition can cause the order to move from created -> new too soon. If that happens Fail Order itself fails. Once it fails, the basket is not recoverable. And the user just sees the checkout skeleton. Fix is to show an error and navigate the user away from the skeleton. If in checkout, take user to cart and if in pdp, no need to do anything

Note: There might be some changes to ssr.js and default.js etc which will not be pushed to develop

Types of Changes

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Breaking change (could cause existing functionality to not work as expected)
  • Other changes (non-breaking changes that does not fit any of the above)

Breaking changes include:

  • Removing a public function or component or prop
  • Adding a required argument to a function
  • Changing the data type of a function parameter or return value
  • Adding a new peer dependency to package.json

Changes

  • (change1)

How to Test-Drive This PR

  • (step1)

Checklists

General

  • Changes are covered by test cases
  • CHANGELOG.md updated with a short description of changes (not required for documentation updates)

Accessibility Compliance

You must check off all items in one of the follow two lists:

  • There are no changes to UI

or...

Localization

  • Changes include a UI text update in the Retail React App (which requires translation)

@amittapalli amittapalli requested a review from a team as a code owner February 5, 2026 19:14
@cc-prodsec
Copy link
Collaborator

cc-prodsec commented Feb 5, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.


const cardCaptureAutomatic = useAutomaticCapture()
const zoneId = useShopperConfiguration('zoneId')
const zoneId = paymentConfig?.zoneId
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payment sheet code has const zoneId = useShopperConfiguration('zoneId') which you removed. I don't know which one is right but I think they should match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed useShopperConfiguration was always coming back undefined for me and I know that the payment config always has a value. So, I assumed its better to use the one that is bringing down the right value

* @param {string} orderNo - The order number to fail
* @returns {Promise<boolean>} - true if failOrder succeeded and basket was reopened
*/
const attemptFailOrder = async (orderNo) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this to a shared place so payment sheet can call it too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, we can. once we go back and start refactoring things a bit more. There is another WI to go over code we want to refactor since both the components are getting to a point where its not as readable. If I do it now, then any local refs that I am using would need to also get added to payment sheet and then we need to do regression on that too as part of the express work item.

const createOrderAndUpdatePayment = async (
basketId,
paymentType,
zoneIdValue,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to pass in a zone id if it's a const above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I will clean that up, don't think we need to pass that as a param.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

},
ORDER_RECOVERY_FAILED: {
defaultMessage:
'Order recovery failed. Please try again or select a different payment method.',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the basket have been reopened in this case? Not sure they can try again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That basket is gone when fail order fails looks like. So you are in this odd state where there is no basket and hence you see that skeleton. I didn't see a good way to reopen a basket from PWA without recreating a basket from scratch which seemed kind of risky if done from the client side. I mean if we go for this retry approach, then we need a way to recreate baskets that are just gone once an order is created. Another reason I also added basketIdRef in sf-payments-express, basically to prevent unmounting when basket becomes null during the failure scenarios

}

const onCancel = async () => {
isPaymentInProgress.current = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In past work on express it was hard but I was able to eliminate the need for a flag like this. The basket was the key to it all, which led to the annoying startConfirming/endConfirming calls. Is there something about Adyen express specifically which makes us need this flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we had was working for Stripe and when I started to integrate Adyen, things started to unmount before I can even complete the process, especially when we are also trying to mitigate the fail order use-cases. startConfirming/endConfirming seem to stabilize the basket. But the main useEffect in Express has other dependencies that can change and perhaps changed for Adyen. If that effect reruns, it will cleanup and unmount things. Because we don't know what causes React to go down that route for different use-cases, maybe having the guard can help. I think we need to further test and see which of the culprits in the dependencies might be triggering the effect to rerun.

}
updatedPaymentInstrument = getSFPaymentsInstrument(expressBasket.current)
} else {
// For Adyen: Update addresses from paymentData before creating order
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we did this in SFRA for Adyen, is that a bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of SFRA but Stripe also does it but it was running all that logic in the onPayerApprove action which I didn't see for Adyen in the SDK. The Order API validates that the basket has the following set already before it creates the order.
-Shipping address
-Billing address
-Payment instrument

const paymentMethodSet = {
paymentMethods: paymentConfig.paymentMethods,
paymentMethodSetAccounts: paymentConfig.paymentMethodSetAccounts
// inject country code into payment method set accounts (Adyen seems to need it but Stripe works with/without it)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is supposed to come from the Adyen account. It needs to be in the Adyen account config info coming back from ecom. Is it not there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is what I see in the SCAPI response for paymentMethodSetAccounts and not seeing the country the way the SDK expects. Are we missing something?

 const initialTransactionInfo = {
      countryCode: vendorAccount.config.country.toUpperCase(),

"paymentMethodSetAccounts": [ { "accountId": "SFAccount880ECOM", "config": { "key": "test_DJR5I4B4RJFEPMEO76L4AGNH6IXDCQH4", "paymentMethods": { "paymentMethods": [ { "brands": [ "amex", "cup", "diners", "discover", "mc", "visa" ], "name": "Cards", "type": "scheme" }, { "configuration": { "merchantId": "50", "gatewayMerchantId": "SFAccount880ECOM" }, "name": "Google Pay", "type": "googlepay" }, { "name": "Pay later with Klarna.", "type": "klarna" }, { "name": "Pay over time with Klarna.", "type": "klarna_account" } ] } }, "live": false, "vendor": "Adyen" }, { "accountId": "HZ74QTLCWMBBL", "config": { "bnCode": "SalesforceCommerceCloud_PPCP", "key": "AVzUrnC6x3Zb4l4PIZSP3yOJaO7SLm9q6zRsJ_2gL8fmzxK-UvJ4sFPi3f0x82U5TMuB4i_EYRRZ5woQ" }, "live": false, "vendor": "Paypal" } ],

// this flag ensures data remains available even if the refetch completes before the cache update.
const {data: basket} = useCurrentBasket()

// Preserves the basketId in a ref so that the component stays mounted even after the basket is consumed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there's something specific for Adyen express we shouldn't need this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to remove that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I realized that startConfirming() was not getting triggered in the right place for the Adyen flow. So fixed and removed the use of basketRef which was also essentially trying to keep the wrapper component from unmounting due to basket becoming null and the start/endconfirming handle it as you mentioned.

I am still keeping isPaymentInProgress around for a bit longer since that is being used to guard the useEffect in the buttons component from re-running

@amittapalli amittapalli merged commit 08c2cf6 into t/team404/sfp-on-pwa Feb 9, 2026
8 of 16 checks passed
@amittapalli amittapalli deleted the amittapalli/pwa-adyen-updates branch February 9, 2026 18:28
rasbhat pushed a commit that referenced this pull request Mar 5, 2026
Support processing adyen payments and handle 409 errors for failure use-cases
rasbhat pushed a commit that referenced this pull request Mar 5, 2026
Support processing adyen payments and handle 409 errors for failure use-cases
rasbhat pushed a commit that referenced this pull request Mar 5, 2026
Support processing adyen payments and handle 409 errors for failure use-cases
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.

3 participants