Handling payments securely in React Native

I’m rebuilding an e-commerce mobile app side project (WooToApp) that requires payment for orders created.

The initial gateway is Paypal, but it’s an important part of the mobile app that there could be many more gateways, so the architecture needs to be re-usable and secure.

Basic architecture

Assumptions

We need to assume that the mobile app context is insecure. It’s easy to decompile a mobile app and extract keys, and it’s easy to copy HTTP requests to update an order as paid. Both of these are deal breakers.

We can assume the payment gateway and the app backend are secure.

With these assumptions, I made this quick architecture map to follow.

Quick explanation

It’s a relatively common pattern – the mobile app signals intention to pay to the backend.

The backend talks to the gateway, and returns the mobile app just enough information to pay (a redirect to a payment page)

The gateway then notifies the backend that payment has been authorised, which updates store and the mobile app.

Note above that the only components doing any sort of heavy lifting are our trusted components (app backend and payment gateway). We don’t trust any sensitive data from the mobile app or the ecommerce store.

Explicitly defining the architecture before starting work (and fine tuning along the way) means it’s easy to get into ‘thinking’ mode and define the secure boundaries and map it out.

In the next sitting when it’s time to write the implementation, the architecture and security considerations are both fresh in your mind AND documented. It’s a lot easier than architecting and reasoning about security along the way.

Payment Handling

I won’t dive into the server side payment handling here. There’s nothing exciting there, it’s just nothing innovative. A nodejs script (the app backend) captures the payment request from the mobile app and creates a payment request that paypal understands. Paypal gives the backend a redirect URL.

In the mobile app, we should a secure frame for the customer to make payment to the gateway. Paypal redirects the user to a URL that notifies the backend that payment was received, which redirects the user to the app thankyou page.

The mobile app has zero knowledge of paypal libraries and integrations. There’s no surface area for the mobile app to be vulnerable. Secret keys are never safe in a mobile application.

Payment UI

This is a pretty important one. The regular webview in React Native is susceptible to manipulation and as far as I know should not be used for any type of third party secure communication in React Native. For example, an app developer is able to inject a JS keylogger. It’s flawed by design.

iOS and Android both make a secure browser context available that is not susceptible to manipulation, which has been abstracted into a useful library here.

Once payment has been made, the gateway success URL redirects back to a deep link for the mobile app (think myapp://order-paid/505). React-Navigation deep linking works with simple code changes to handle this.

There’s a small issue to work around – the popped out web browser context successfully causes the deep link to fire and navigate to the correct page in the mobile app, but you can’t see it because the web browser context stays open on the top.

The Order Paid page will always be the result of a deep linked action, so we can just hide the browser when the page initiates.