Skip to content

    Decrypting Card Payload for Shopify Payments

    Decrypting Card Payload for Shopify Payments

    When this video was recorded, Shopify Payment App documentation was in its early stages and only depicted Ruby code samples. 

    Our goal was to support an early adopter of the program by leveraging their existing outsourced Node.js-powered cardholder data environment (CDE).

    Watch the video recap or read on for the full story:


    The Problem

    In the technical discovery call with a customer, they mentioned that they got into the Shopify Payments App program. This enables them to access the encrypted consumer's credit card, as long as they receive it in a PCI-compliant environment.

    The way this works is: when a consumer enters their credit card information on the checkout page, Shopify will call the Payment App passing the payload, which contains encrypted card information. The App must respond with a 200 OK, and can asynchronously do the payment processing. When it is done, they may call Shopify to finalize the Payment, who will notify the user.

    At first, start_payment_session feels like a webhook, and a job for what we call here at Basis Theory the Inbound Proxy, which is programmable. Without going into too many details about how this works, let’s just say you can run serverless functions to transform the incoming payload before it hits your servers.

    The other interesting requirement is that, in order to confirm Shopify's identity, they make that request using mTLS, while employing their self-signed certificate. So our platform team configured a particular DNS record to accept it, by checking it against Shopify's self-signed CA certificates. 

    Easy, right?

    Now for the job to be done, we had another challenge: securely perform ECIES hybrid decryption in our Proxy to obtain the plaintext card information, tokenize it, and send the token ID to our customer servers, so they can later process the payment with any downstream acquirer that they prefer. 

    The updated diagram looks roughly like this. 


    Again, the customer completes the checkout in Shopify, that using mTLS will call the Basis Theory Proxy passing the encrypted cardholder data. The Proxy decrypts and tokenizes the credit card, and calls the Payment App sending the original payload, but replacing the card information with a token. The Payment App responds with a 200 OK which is forwarded back to Shopify. Now the Payment App can connect to any acquirer of their preference for doing the actual payment processing. Once that is done, it will finalize the payment against Shopify, which will notify the customer.

    The Solution

    Shopify has an example of how to perform the decryption using Ruby. But our Proxy transform functions only operate in Node.js environment today. 

    My obvious first attempt was to look for a popular NPM package that was capable of decrypting the payload following Shopify's specifications, without too much boilerplate code. See, this code was going to be shared with our client and needed to be as short and easy to maintain as possible, so hopefully we could replicate the pattern for other customers, too.

    Since no NPM package did what we needed, I started digging into ECIES Hybrid encryption and tried to replicate the code in Node.js. The steps looked very similar to Apple Pay and Google Pay token debundling, for which I wrote a decryption package in the near past. 

    However, each company customizes its specifications, using different key lengths, elliptic curve, and other parameters. I can't personally tell if one or another is more or less secure, so I leave that to mathematicians.

    It all comes down to 4 steps: 

    1. Compute a shared secret between your private key and the client-given ephemeral public key, using Elliptic Curve Diffie-Hellman (ECDH)
    2. Derive a symmetric key pair - which is a MAC Key and an Encryption Key - from the shared secret, using the specified KDF - which is a key derivation function
    3. Use the MAC Key to verify the message signature or tag
    4. Use the Encryption key to decrypt the payload

    After many hours in Stack Overflow and countless prompts in Chat GPT and Bard, the POC code finally worked. I was successfully verifying the encrypted code payload and obtaining the plaintext card number, expiration, and CVC. 

    So I did some code polishing, added key rotation support, and published a Shopify JS package under the company's open-source repositories. We may end up consolidating all encryption utilities in a single repository in the future, but for now, our customers and the community can make use of this maintained package in their Node.js environments.



    Resources used in our research:

    Subscribe to the Blog

    Receive the latest updates straight to your inbox