Closed jarekcieslak closed 8 months ago
Hey @jarekcieslak ,
Thanks for reaching out and for your proposal. One of the considerations when working ApplePay is that it doesn't work well with iframes indeed, and that was by design. To overcome this problem the iframe origin must match with the parent origin, which is not always the case.
On Safari 17, Apple released a feature which allows ApplePay to work inside the iframe. You must set the attribute allow="payment"
in the iframe, and it should work seamlessly.
We prefer to rely on the Safari feature which is already available rather than creating an specific configuration that will change the ApplePay setup behavior, which can lead to more issues in the future.
Cheers.
Hi @ribeiroguilherme,
Thank you for responding to my proposal.
On Safari 17, Apple released a feature which allows ApplePay to work inside the iframe. You must set the attribute allow="payment" in the iframe, and it should work seamlessly.
Indeed, that's necessary. However, even with allow="payment"
set on the iframe, Adyen's web integration will internally create a payment session using the iframe's origin, not the origin of the host page. Please take a look here: link to the code.
This situation can lead to a problem where:
In such a situation, the payment will fail, and Apple devices will display "Payment Not Completed." It would be great to have an option to specify the domain for which the Adyen Apple Pay session is created. We conducted a proof of concept (PoC) by forking the library and changing a specific line, and we were able to process payments. Will adding such an option to adyen configuration be possible?
Hi @jarekcieslak ,
Indeed, that's necessary. However, even with allow="payment" set on the iframe, Adyen's web integration will internally create a payment session using the iframe's origin, not the origin of the host page.
But that is the desired behavior, no? If you are hosting your Checkout flow in another page/domain, it must be a self contained page which will create the session, perform the payment, etc. Am I missing something?
Anyway, I will double-check about the iframe flow using two different domains and will get back to you on this
Anyway, I will double-check about the iframe flow using two different domains and will get back to you on this
Thank you 🙏 I'm happy to provide more information if needed.
But that is the desired behavior, no? If you are hosting your Checkout flow in another page/domain, it must be a self contained page which will create the session, perform the payment, etc. Am I missing something?
I was expecting the same, and I think in the majority of payment flows, that would be the desired behavior. However, with Apple Pay, it seems to be different. Here's what I was able to observe:
I believe this discrepancy might be because Safari uses the top page URL to verify the payment, rather than the iframe's URL.
Hey @jarekcieslak,
Could you share the example of URL's that are being used in this case? (The parent URL and the iframe URL).
I'd like to understand how they relate to each other, and then I can try to recreate your setup locally to test it and get more insights about the problem.
Hello @ribeiroguilherme 👋
I've prepared an example to illustrate the issue, accessible here: https://demo.ticketing.run/adyen.html?tck_version=t2038. To reproduce the problem, follow these steps:
Please let me know if that's enough or if you need more information
PS. In the example, adyen web-dropin is connected to adyen's testing environment. If you need a production
example I can prepare it.
Hey @jarekcieslak .
We reached out to Apple to ask about the iframe integration guide and what are the do's and don't. In the following scenario that you have, the recommended approach is to communicate to the parent frame through postMessage
, requesting the ApplePay session from there and not from inside the iframe.
Taking this into consideration, it would not make sense for us to let merchants tweak the domain url to the parent window. The correct approach in this case would be to implement the onValidateMerchant
, and then perform the ApplePay session creation in the parent frame.
But, if you want to proceed with the changes you did and you are sure they work well, perhaps one approach that could work is to implement the onValidateMerchant
callback, and then copy the code that you mentioned before (here), and change the domainName to use the parent one. In this case, you would implement the callback almost identical as how the library internally implements it, but you would change the domain part. Does that work for you?
@ribeiroguilherme, thank you for your thorough review.
Option 1 This is feasible, but I have a few questions. Just to confirm - the process would look something like this:
Option 2 If I understand correctly, by implementing a custom onValidateMerchant hook, we would simply replace the default validateMerchant implementation https://github.com/Adyen/adyen-web/blob/789b285b86e3eee43c69d6feb3da0f12960840a9/packages/lib/src/components/ApplePay/ApplePay.tsx#L97-L114 Is that accurate?
Thank you once again for a very detailed investigation 🙏
In the option 1, you would be implementing the approach recommended by Apple. In this case, you need to make this communication between iframes using the onValidateMerchant
through postMessage
. And then in the parent page, perform the ApplePay session creation and return the data back to the child iframe. It is important to mention that I am not entirely sure that you will be able to use the Adyen endpoint in this case, because the domain requesting the session creation will be another domain that is not your Checkout domain, so in this case you might need to implement your own endpoint.
In the option 2, you would be implementing the onValidateMerchant
inside your child iframe and skipping the whole communication between iframes.
I highlighted the option 2 since you said in your first message that you forked our library and you managed to make the session creation from within the child iframe directly, just by changing the value of the domainName
property. This approach is not recommended by Apple though, but if that works for you, you can try it out.
In that case (option 2), you would adapt the code that is on the validateMerchant
function into your onValidateMerchant
callback, to make the request to Adyen endpoint. We don't expose any utility function for this, but you can replicate on your side easily by copying-paste and implementing the http request to the endpoint. Disclaimer: I didn't test myself, but I believe it should work.
Thanks for explaining! I'll give it a try and let you know what was the right solution for us.
Hey @jarekcieslak - I am closing this issue for now, as there is nothing we can do from our side. I hope the options mentioned above will help you guys to fix the problem. Cheers
Hi @ribeiroguilherme 👋
Sorry for responding so slowly. I'm happy to report that I was able to make a successful Apple Pay payment using Option 2. I'll refine the code and post an example here for posterity. Thanks for investigating and giving great suggestions 🥇
Hello once again 👋 For posterity, I'm leaving a code sample summarising the discussion.
import AdyenCheckout from "@adyen/adyen-web";
import type { CoreOptions } from "@adyen/adyen-web/dist/types/core/types";
import type { ApplePaySessionRequest } from "@adyen/adyen-web/dist/types/components/ApplePay/types";
const adyenWebDropInConfiguration: CoreOptions = {
paymentMethodsConfiguration: {
applepay: {
onValidateMerchant: (resolve, reject, _validationUrl) => {
const applePayPaymentMethod =
adyenCheckout.paymentMethodsResponse.find("applepay");
// Extract applePay merchantId
const merchantId = applePayPaymentMethod.configuration?.merchantId;
// Extract applePay merchantName
const merchantName = applePayPaymentMethod.configuration?.merchantName;
// NOTE: This is the main difference from the original code.
// domainName example values: adyen.com, docs.google.com
const domainName = new URL(URL_OF_THE_PAGE_CONTAINING_IFRAME).hostname;
// The initiative value is used to determine the type of Apple Pay transaction.
const initiative = "web";
const path = `${adyenCheckout.loadingContext}/v1/applePay/sessions?clientKey=${ADYEN_CLIENT_KEY}`;
const request: ApplePaySessionRequest = {
displayName: merchantName,
domainName,
initiative,
merchantIdentifier: merchantId,
};
fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
})
.then((response) => {
return response.json();
})
.then((response) => {
// Decode base64 encoded response.data
const decodedResponse = atob(response.data);
// parse the string back to JSON
const session = JSON.parse(decodedResponse);
// continue the payment process using the session
resolve(session);
})
.catch((error) => {
reject(error);
});
},
},
},
};
const adyenCheckout = await AdyenCheckout(adyenWebDropInConfiguration);
adyenCheckout.create("dropin").mount("#dropin-container");
Hello @jarekcieslak,
thanks for the exhaustive explanation and for the code example, it is a great resource. In my company we're in a similar situation as yours. Sometimes our checkout page (on which we render the drop in component) is embedded in an iframe.
We have not yet tried either options mentioned here but we're facing a slightly different problem. When the component is rendered in an iframe the Apple Pay button is not visible and we're getting
InvalidAccessError: Trying to start an Apple Pay session from a document with an different security origin than its top-level frame.
I thought that allow=payment would have fixed that error as mentioned here
It is worth mentioning that in our case the iframe domain is always a subdomain of the main page domain.
Thanks
@domspektrix, thank you for your kind words 🙏. I believe the issue you're encountering could potentially be resolved using the same technique. In my opinion, the error you're seeing arises due to this specific line of code: https://github.com/Adyen/adyen-web/blob/789b285b86e3eee43c69d6feb3da0f12960840a9/packages/lib/src/components/ApplePay/ApplePay.tsx#L98
and
const { hostname } = new URL("https://domain.com"); // domain.com
const { hostname } = new URL("https://subdomain.domain.com"); // subdomain.domain.com
Since you're utilizing the Adyen Web Drop-in component with the Apple Pay button from a subdomain, you're effectively initiating an Apple Pay session for 'subdomain.domain.com'.
It seems that Safari internally verifies the URL visible to the user (URL in Safari's address bar) and compares it with the domain of the Apple Pay session. If there's a discrepancy, an error occurs, and from my understanding, this describes your current situation. Let me know if that helps and Godspeed 🤞 (it's quite difficult to debug these issues )
I will try it today and let you know. What's weird though is that we are seeing a different error from you as in the button is not visible at all, whether for you it was but the payment cannot be completed. On which device and browser version did you get that behavior ?
Thank you
Indeed that's different behavior, I remember seeing this error at some point though. A couple of important remarks:
allow="payment *"
on the iframe levelDetails of my environment are:
OS: Mac OS 14.3.1
Safari: Version 17.3.1 (19617.2.4.11.12)
Hope that helps 🍀
Thank you,
We had that configuration already in place as well but it is still not working.
I tried your demo application at https://demo.ticketing.run/prodtest.html from an IOS device and I get the same error as on my application
InvalidAccessError: Trying to start an Apple Pay session from a document with an different security origin than its top-level frame.
@domspektrix what's your iOS version? I just checked on an old iPad and got Trying to start an Apple Pay session from a document with a different security origin than its top-level frame.
error in the console. Most likely this solution will work only with relatively new Safari (17+). If you want to debug it together maybe we can create a shared Slack channel.
Hello Jack,
sorry for my late reply. I confirmed it was a version issue. Apple Pay via iframes only work from version 17+ due to this recent release
Once I upgraded the IOS version to 17 it worked just fine.
One thing I noticed on the Adyen test platform is that Apple Pay still works without hosting the domain verification file on the parent page domain. I will confirm this is just a TEST platform behavior with Adyen and that the domain verification file needs to be hosted both on the iframe domain and the top page domain
Thank you
Hi @domspektrix. That's great. I'm glad that it's working 🎉 Did you end up using this: https://github.com/Adyen/adyen-web/issues/2351#issuecomment-1971463849 trick? Or it just worked for you out of the box?
Regarding the Adyen domain verification file - I have a feeling that on the staging environment, it's not needed at all but I don't have a proof 😅
@ribeiroguilherme We've started receiving some live env Apple Pay payments, so I can confirm that the solution works 💪
Hello @jarekcieslak,
it didn't work out of the box. We used your suggested solution of overriding the onValidateMerchant
function and to create the Apple Pay session against the domain of the parent page
Thanks
I am glad to hear it works @jarekcieslak , and thanks again for sharing your solution here 💪
Hi @jarekcieslak
I have a case where iFrame is nested in pages with a different domain as our customers have different domains. In this case, is it possible to implement option 2 in which the domain name is overridden at onValidateMerchant or must it coincide with the real one on the top page? Adyen and Apple do not support wildcards and in this case the registration of the URL of each new customer is not a particularly working option. Is it possible to register and validate only the domain of iFrame content?
hi @Viktorstst
I have a case where iFrame is nested in pages with a different domain as our customers have different domains. In this case, is it possible to implement option 2 in which the domain name is overridden at onValidateMerchant or must it coincide with the real one on the top page?
That's exactly my setup. I have an app that is embedded on various domains via an iframe. I'm using option 2 to override the domain in onValidateMerchant.
Adyen and Apple do not support wildcards and in this case the registration of the URL of each new customer is not a particularly working option. Is it possible to register and validate only the domain of iFrame content?
Sadly, what you need is to make sure each one of your cusomers have uploaded https://your-domain.com/.well-known/apple-developer-merchantid-domain-association AND THEN enroll customer's domain in the apple pay via https://docs.adyen.com/api-explorer/Management/1/post/merchants/_merchantId_/paymentMethodSettings/_paymentMethodId_/addApplePayDomains. AFAIK it's not possible to enable apple pay once for all the domains.
Hope this helps 🤞
@ribeiroguilherme, thank you for your thorough review.
Option 1 This is feasible, but I have a few questions. Just to confirm - the process would look something like this:
Inside the iframe, we would use an onValidateMerchant hook that sends a message to the parent page.
- I assume we need to include all this information in the postMessage https://github.com/Adyen/adyen-web/blob/789b285b86e3eee43c69d6feb3da0f12960840a9/packages/lib/src/components/ApplePay/ApplePay.tsx#L99-L103 Is that correct?
On the parent page, we receive the message and then send a request to Adyen's servers to create a payment session using the correct domain of the parent page.
- I wonder if we can use the adyen-web package for this, or am I mistaken? Should we just make a direct request instead?
Option 2 If I understand correctly, by implementing a custom onValidateMerchant hook, we would simply replace the default validateMerchant implementation
Is that accurate? Thank you once again for a very detailed investigation 🙏
would these options work for the hosted checkout cart option thats in an iframe?
Is your feature request related to a problem? Please describe.
Currently, when the Adyen web drop-in component is placed on a page embedded in an iframe, the Apple Pay button is visible. However, upon tapping it, the Apple Pay modal window is immediately dismissed with a message stating "Payment not completed."
This issue arises because the Adyen web drop-in component internally creates an Apple payment session using the URL of the iframe, rather than that of the parent page. Refer to this link. The Apple Pay modal window opens in the context of the parent page, leading to a domain mismatch.
This poses a problem for me as my web app, which provides a checkout experience based on the web drop-in component, is rendered inside an iframe. Naturally, I'd like to utilize Apple Pay within this iframe.
For proof of concept, I forked the adyen-web library and modified the URL used to create the Apple Pay session to match that of the parent page, which resolved the issue. Consequently, I was able to process Apple Pay payments from within the iframe.
Describe the solution you'd like I would like the ability to override the default behavior. When passing the Checkout configuration object, I want to specify the URL that should be used to create the Apple Pay session, enabling me to use Apple Pay within the iframe.
Describe alternatives you've considered I've thought about creating an Apple Pay session using the onValidateMerchant hook. However, this approach necessitates the creation of a custom backend endpoint and complicates the entire integration process.
Additional context