Web Payments - Deep Dive

Web Payments - Deep Dive

This is a follow up on my previous article on Payment Request API. That was almost a year ago, and I've had some time to further my efforts into implementing a fully featured, production ready web payment solution for Fanatics.

We are still "not there yet", but we're very, very close to launch it in production. I wanted to share some tips + things that I've learned from making mistakes (because that's the best way to learn) while working on this project. Also, a shout-out to the engineers from Google Pay and Cybersource who have helped me in resolving some of these issues, and answered crucial pieces of information that's hard to find online.

To reiterate the obvious, our pilot project is to implement Google Pay via Payment Request API, with Cybersource as our payment gateway.

So, here are some of the hidden pitfalls that you may come across, if you ever do the same:

Payment Method and Payment Details Schema

Like me, if you happen to make a mistake in the schema for the Payment Request object, not only would the Payment sheet not open, you'll not get any indication as to why.

My only tip is to be very careful while following the guideline docs and double, triple check if the schema matches perfectly to what the Payment Request expects.

If you are still seeing errors like "Unexpected Developer Error, please try again later" in Google Pay sheet, remember that it is simply another web page - just right click on it, "inspect" and go to console to see the reason for that error!

PaymentRequest.canMakePayment()

This issue was most frustrating of them all, and I had multiple back and forth with Google engineers and StackOverflow answers, before I reached a state that I'd call a solution.

The canMakePayment() function checks whether the browser has the capability to make a payment using the method that you support. For example, Apple Pay button should show up on Safari (iPhone or Mac), whereas a Google Pay button should show up on Chrome (on Android phones or Desktop), and neither of these should show up on Edge. All three browsers support PaymentRequestAPI. Even though the code is same for all browsers, it would behave differently, depending on which browser the user is on.

This function returns a promise, and when that resolves, it returns a boolean to indicate whether the payment method is supported or not. It may also throw exception in certain scenarios.

No alt text provided for this image

While all of these are mentioned in the specs and the guideline docs, the problem I was facing was that once in every few attempts, the returned promise from this function wasn't resolving at all- not throwing an exception, but stuck in a state of limbo. Most of the time, it would work though.

Chrome developers told me that this can happen when the Payment Request object cannot be deserialized, or Chrome is not able to reach the Payment Provider, or there's some issue with the merchant information.

Tips:

  1. Do not call "canMakePayment" all the time - the purpose of this call is to know whether the browser is capable of making the payment. Once that information has been fetched, use localStorage to persist that information, and on next attempts, check localStorage instead of calling "canMakePayment"
  2. Check if any other library is making this call - in our case, there was another payment solution in the page (Visa Checkout) that was making this same call, with their own Payment Method. I assume they were doing it to gather information from the client (whether the browser supports their own payment solution). This is bad for me, since back to back calls to "canMakePayment" would cause it to throw exception. So I used the proxy pattern to block window.PaymentRequest completely. 😈
if (window.PaymentRequest) {
  (function(proxied) {
     window.PaymentRequest = function() {
        if(arguments[3] && arguments[3] === myArg)
           return new proxied(...arguments);
     };
   })(window.PaymentRequest);
}

I would not really recommend this last one, as I'm still debating with myself against it. If the 2 tips mentioned above do not solve this problem, then set an interval and recheck after 500ms. For some reason, this works. Once it does, break out of the interval and go to step 1.

Encoding the Payment Token and Authorizing Payment

This section is very typical with Cybersource. If your payment provider is someone else, this would not apply to you.

Completing a payment in Google Pay sends back a tokenized card via Cybersource (guidance doc):

{
  "requestId": "5d561f4a-c37a-4ce2-9a38-c614f1e75c7a",
  "methodName": "https://google.com/pay",
  "details": {
    "apiVersionMinor": 0,
    "apiVersion": 2,
    "paymentMethodData": {
      "description": "Amex •••• 1004",
      "tokenizationData": {
        "type": "PAYMENT_GATEWAY",
        "token": "{\"signature\":\"MEUCIQCLyWBXDwWu15hyPlDtBQPdcAtj84duCal07e/kiUuFsQIgb4++733haiR84nin79kPrE+NcpdI/OONAzsajpsWf0\\u003d\",\"protocolVersion\":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"sdNXjp8riOfxIBUMx+VkCuJHimv8Kt4lStigQ7Q5IFJvmqEcl8oc6ZG6zTE6iQmQE1O/0mPFhf+aiwVR1P0otwVS9AQzitdH8Pkukr9yOoWvWwa3zHZFDzvoQGQ1f5WmF8r5PQW4/S3fZB87yPsKG4JwY/ovRhgPxVdI+L2zJqIzdrRJlrpUQevH5CE1Ht7auKm4Vy/40EjIrsNmULVnJ83Y0BBJpJNsH3n+5qJKC8yjLelM5fBoE2bl+f1VwE1GJMeA9zxF1cuBrGsAXkOdgngrcEWwKqbnli9V2RI/zpq7hAyw+GZ6U1QmgBRNJ/tfJ1/z1wVc7ZWp30tWH0um+7xU/UD+8PonUJHDPRdS9rU/hh8eqPMkpUlKgWKiZK5ePViAO+UNSKJcSZAXxjfzZnD69u9x01+QMup7p5NCfV4EKtZW30yPNe6KCiqTvqa4CPsW\\\",\\\"ephemeralPublicKey\\\":\\\"BEVYVbkssWFzwQh1VMcUgIgFJEI7F6ZcySWWSKbSWiAE+fknGn1doTl0oFtXxz8Wr04RvXoruTZMburGDqQbC+I\\\\u003d\\\",\\\"tag\\\":\\\"BqU3f9jPXeLn7owSpwlflof1Yl8v+4l5VK4uKqPZlxg\\\\u003d\\\"}\"}"
      },
      "type": "CARD",
      "info": {
        "cardNetwork": "AMEX",
        "cardDetails": "1004"
      }
    }
  },
  "shippingAddress": null,
  "shippingOption": null,
  "payerName": "Soham Bhattacharjee",
  "payerEmail": "soham.onlyn@gmail.com",
  "payerPhone": null
}
    

When sending this information to Cybersource, even though this part of the document says send Base64 encoded "encryptedMessage", but that will not work. Instead, Base64 encode the entire "token" string and send that in the request, otherwise Cybersource will just complain that they are unable to decrypt the encrypted information.

Testing in Sandbox

I've worked with a few other payment solutions - Apple Pay, PayPal, Visa Checkout, etc. All of them have "Test Cards" or "Sandbox Accounts" to play around in the sandbox environment, and place as many test orders as you like. When I reached out to Google Pay to get a sandbox account or a way to add a test card, this is what they told me:

No alt text provided for this image

Google Pay does not let you use test credit cards (they run a real time auth check to validate any credit card being added). Instead, they insist on using real, live credit cards in test environments. When the Payment is authorized in the browser, they simply discard the real card, and send back a test card token!

From my experience, I've seen that the test token is of a Visa card. I am yet to confirm this with Google, whether they always send back a Visa token, irrespective of the original card type used. I'll update this section once I get back that information.

Address Validations

While it is very easy to send errors back to the payment sheet in case of shipping address validation errors, or unable to ship errors, etc, there's no way (currently) to validate the billing address and send back errors for that.

Does not work in India

Google Pay in India has a completely different solution than the rest of the world. The Payment request goes through https://tez.google.com/pay instead of https://google.com/pay. The schema for the payment request is also very different. This guide will not work in India.

Google Pay would not work on Chrome on iPhone

Even though Google Chrome supports Payment Request API, on iPhones, calls to https://google.com/pay will get blocked.

No alt text provided for this image

It would be better to encapsulate the payment request creation in a try - catch statement.

Don't do any of this for Apple Pay

We initially considered doing the pilot project with Apple Pay, instead of Google Pay. however, the native support for Apple Pay is far more sophisticated in terms of error management and overall stability than a solution via Payment Request API. Not to mention that Apple Pay requires an additional "onMerchantValidation" method to be defined on the Payment Request, which is not a part of the W3C spec.

Please use/continue using the Apple Pay JS for implementing web payments with Apple Pay, and I'd strongly advice against doing it via Payment Request API (at least till the time Payment Request API becomes more robust to handle all the use cases).

Epilogue

Web Payment is no longer my pet project. I'm trying as best as I can to make it production ready. I'll be learning more in the coming weeks as we roll this out. If you are facing any challenges, DM me, and I'll be happy to help, as best as I can, to resolve your issues.

To view or add a comment, sign in

More articles by Soham Bhattacharjee

  • Payment Request API

    I hate typing on my mobile. Especially when I have to spell out my (rather long) full name, my credit card details, my…

  • Keeping Your Promises

    I have come to admire the JavaScript Promise object over the last few weeks, and also dread it. I'm trying to share two…

  • Working with Visual Studio Code and Git

    Small background story - I’ve been a Microsoft .Net developer throughout my career.

  • Git Bisect to the rescue.

    TL;DR Version: This is a story (use case) about a time when git bisect saved me from falling to the pits of debugging…

    1 Comment
  • Workaround for Sticky Footers on iPhone6 Safari

    TL;DR version: iPhone 6 + Safari + sticky footer and scroll = mess. Proposed solution: listen to the event (tricky)…

Others also viewed

Explore content categories