DEV Community

loading...
Cover image for Stripe Card Payment in React Native without 3P Library

Stripe Card Payment in React Native without 3P Library

impdp profile image Pradeep Annadurai ・10 min read

Payments in React Native App

Peer to Peer(P2P) Payments from an app to app is 'one-touch' magic nowadays. Thanks to the enhanced SaaS and end-user software. For developers, integrating payments in the app and website is strenuous and painful, even by following the official documentation. Two possible reasons are improper and fatiguing docs explanation and lack or no support to the end framework from the payment gateway provider. Considering the cross-platform apps, the bar is even lower. Hardly they support them, especially React Native.

Stripe is one of the popular payment service providers. It is an America based financial service company which provides Software as a Service. They provide services such as Google Pay/Apple Pay, bank payments, card payments, etc., Looking into development support from the Stripe, they provide official module and SDK for browser-based JavaScript Apps and Android/iOS apps respectively and APIs for server-side integration. To Integrate Stripe in React Native, there are some practices which are being followed by developers and companies.

Implement with Tipsi-Stripe or Native Modules

Many novice developers or stripe users could get to know about Tipsi-stripe when searching "React Native Stripe" in the search box, as stripe has no official support for React Native. One of the reasons for using Tipsi-Stripe is moderate to good SCA/PCI compliance, as it is actually a wrapper around native SDKs. At the same time, one can avoid that for lack of customisation. Customisation in the sense of card inputs.

All mandatory APIs/SDKs of the Stripe should be run in the server-side. Here, We care about 'inputs' only, as it relies on client-side. In addition to SDK support, stripe bundles the SDK/Modules with Stripe Elements which are ready-made with SCA/PCI compliance. The problem is if we want to customise the Tipsi-Stripe Inputs, we have to sacrifice the Strong Customer Authentication and at the same time Stripe Elements are important for the card inputs.

According to a source, they were moved to start the development of the RN module recently. There is also a native way to integrate Stripe in React Native with Android/iOS SDKs by bridging them to JS with Native Modules. But this is not everyone's cup of coffee. Hardly any RN developers know both native languages (Java and Swift). In this article, I'll try to get across the simple way of using stripe card elements in the RN with React-Native-Webview (If you need customisation of Inputs, this is the best way as of now apart from bridging).

Please scroll below, if

  • you don't want any 3P libraries for inputs

  • your development is in the paths of the project's web development in your company (Because Android/iOS SDK's payment flow works differently from Stripe.js).

  • you are fine with RN Webview as the viewport is actually a native in-built Webview under the hood.

Integrating Stripe Elements in the React Native Application

In this method, the payment flow from the user clicking the proceed button to money debited from the card will be taken care by stripe APIs which will be running in the server. The card input will be handled by Stripe elements.

What will happen in the process:

  1. In Stripe, the Stripe Customers should be created in the user registration process of the app or in the checkout page for anonymous users.

  2. Card Payment Method will be created and optionally/mandatorily will be attached to the customer based on the app's feature.

  3. Finally the card will be charged with Payment Intent by passing the payment method id, customer id and payable amount to the API. (This is optional, we can also create the payment intent in the start of checkout and charge with Charge API)

Android

Create a folder with any name, here we are naming it as 'StripeWebView' in the following path "android/app/src/main/assets" and add following files - index.html, index.js and style.css.

iOS

Create a folder with any name, here we are naming it as 'StripeWebView' in the root of the project (not in the ios folder) in the following path "src/assets" and add following files - index.html, index.js and style.css.

Create Stripe Account:

Before adding the code to the above files, log in to dashboard and create an account for development.

While creating, Please select the country where the app merchant is based on. Because Stripe has limited and secured transactions for countries like India and account based on India.

After logging in, get the API keys from Home by clicking the "Get your test API keys" accordion. It will give Publishable Key (for client-side) and Secret Key (for Server-Side).

Then add the following code:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stripe - Checkout</title>
    <!-- add your required font which is used in the app -->
    <link href="https://fonts.googleapis.com/css2?family=Mulish:wght@200;300;400;500;700&display=swap" rel="stylesheet">
    <!-- add the stripe.js library v3 -->
    <script src="https://js.stripe.com/v3/"></script>
    <!-- link to style.css folder -->
    <link rel="stylesheet" href="./style.css">
</head>

<body>
    <form action="/charge" method="post" id="payment-form">
        <div class="form-row">
            <!-- start of card inputs holder -->
            <div class="cardHolder">
                <!-- card holder name -->
                <input id='CardHolderName' class="StripeName" placeholder="Cardholder Name" />
                <!-- card number input -->
                <div id="card-element1">
                    <!-- A Stripe Element will be inserted here. -->
                </div>

                <div class="cardVal">
                    <!-- card expiry date -->
                    <div style="width: 100%; margin-right: 10px;" id="card-element2">
                        <!-- A Stripe Element will be inserted here. -->
                    </div>
                    <!-- card cvv -->
                    <div style="width: 100%; margin-left: 10px;" id="card-element3">
                        <!-- A Stripe Element will be inserted here. -->
                    </div>
                </div>
                <!-- button to save card -->
                <button class="submit">Save Card</button>
                <!-- Used to display form errors. -->
                <div id="card-errors" role="alert"></div>
            </div>
            <!-- end of card inputs holder -->
        </div>
    </form>

    <!--Path to Javascript file which will handle the stripe elements input -->
    <script src="stripe.js"></script>

    <!-- To send the data to App or to receive the data from the app by using following DOM -->
    <script>
     //Receive message from the app
        document.addEventListener("message", function (data) {
     //Should be the String data
            console.log(data.data) 
        });
     //Send message to app
        window.postMessage('hi')
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

style.css

button {
  border: none;
  outline: none;
}

button:hover {
  opacity: 0.5;
}

label {
  font-family: 'Mulish', sans-serif;
  margin-bottom: 10px;
  font-size: 15;
  font-weight: 500;
}

body {
  background-color: #fbf7f4;
}

.StripeName {
  box-sizing: border-box;
  height: 40px;
  padding: 10px 12px;
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: 100px;
  font-size: 16px;
  margin-bottom: 10px;
  background-color: white;
  font-family: 'Mulish', sans-serif;
  box-shadow: 0 1px 3px 0 #e6ebf1;
  -webkit-transition: box-shadow 150ms ease;
  transition: box-shadow 150ms ease;
}

.StripeName:focus {
  outline: none;
}

.StripeName::placeholder {
  color: #aab7c4;
}

.StripeElement {
  box-sizing: border-box;
  height: 40px;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: 100px;
  background-color: white;
  box-shadow: 0 1px 3px 0 #e6ebf1;
  -webkit-transition: box-shadow 150ms ease;
  transition: box-shadow 150ms ease;
}

#card-errors {
  font-family: 'Mulish', sans-serif;
  margin-top: 10px;
  font-size: 15;
  color: red;
  font-weight: 500;
}

.StripeElement--focus {
  box-shadow: 0 1px 3px 0 #cfd7df;
}

.StripeElement--invalid {
  border-color: #fa755a;
}

.StripeElement--webkit-autofill {
  background-color: #fefde5 !important;
}

.cardHolder {
  margin: 20;
  box-shadow: 0 1px 3px 0 #e6ebf1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 25px 20px;
  background-color: #fbf7f4;
  border-radius: 15;
}

.cardVal {
  margin-top: 10px;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.submit {
  border-radius: 100px;
  background-color: #0db41b;
  margin-top: 10px;
  padding: 15px;
  color: white;
  font-family: 'Mulish', sans-serif;
  font-weight: 700;
  justify-content: center;
  display: flex;
  width: 100%;
  align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

index.js

// Create a Stripe client.
var stripe = Stripe(
  'pk_test_51HMSMIB2aw923XhBAjlVUnus2fvYnq0jgaXKGBpKlzBeazoSfSwBsTCBPt79vmKQiwTAsKud98voORkxQ9H2W5Ao005TmW0VfM',
); // Publishable test API key obtained from the Stripe dashboard

// Create an instance of Elements.
var elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
// The below values are for demo purpose use your required style values.
var style = {
  base: {
    color: '#32325d',
    fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
    fontSmoothing: 'antialiased',
    fontSize: '16px',
    '::placeholder': {
      color: '#aab7c4',
    },
  },
  invalid: {
    color: '#fa755a',
    iconColor: '#fa755a',
  },
};

// Create an instance of the card Element.
// Card Number Input Element
var cardNumber = elements.create('cardNumber', {style: style, showIcon: true});
// Card Expiry Input Element
var cardExpiry = elements.create('cardExpiry', {style: style});
// Card CVV Input Element
var cardCvc = elements.create('cardCvc', {style: style});

// Add an instance of the card Element into the `card-element` <div>.
// mount function will mount the stripe elements in the divs with following ids.
cardNumber.mount('#card-element1');
cardExpiry.mount('#card-element2');
cardCvc.mount('#card-element3');

// Handle real-time validation errors from the card Elements.
cardNumber.on('change', function (event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

cardExpiry.on('change', function (event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

cardCvc.on('change', function (event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

var form = document.getElementById('payment-form');
form.addEventListener('submit', function (event) {
  event.preventDefault();
  var displayError = document.getElementById('card-errors');
  var nameRegx = /^[a-zA-Z\s]*$/;
  //regex to allow only alphabet value in the card name input
  if (
    nameRegx.test(document.getElementById('CardHolerName').value) &&
    document.getElementById('CardHolerName').value.length > 0
  ) {
    window.postMessage('loading');

    /*Stripe Element's method to create payment method. In the official API docs, there is a way to create a payment method with API. That should not be followed in the live app.*/

    stripe.createPaymentMethod({
        type: 'card',
        card: cardNumber,
        billing_details: {
          name: document.getElementById('CardHolerName').value,
        },
      })
      .then((d) => {
        console.log(d);
        var displayError = document.getElementById('card-errors');
        if (d.error) {
          displayError.textContent = d.error.message;
          window.postMessage('error');
        } else {
          //On Success, we are sending the payment method id to app
          window.postMessage(`${d.paymentMethod.id}`);
        }
      })
      .catch((e) => {
        console.log(e);
        window.postMessage('error');
        var displayError = document.getElementById('card-errors');
        displayError.textContent = 'Some error occurred. Try again';
      });
  } else {
    displayError.textContent = 'Please enter the valid name';
  }
});
Enter fullscreen mode Exit fullscreen mode

To implement the Webview in the React Native component, add the following in your CardPayment.jsx or AnyName.jsx you have given.

// To receive the payment method id from the Webview 

const html = require('../assets/stripeWebView/index.html');

const onMessage = (data) => {
    if (data == 'loading') {
      // Show progress bar
    } else if (data == 'error') {
      // display error notifcation
    } else {
      // On Success, attach and pay or attach the card to 
         customer
    }
};

const injectedJavascript = `(function() {
    window.postMessage = function(data) {
      window.ReactNativeWebView.postMessage(data);
    };
})()`;

<WebView 
// Conditionally add the source path
        source={
              Platform.OS === 'android'
                ? {uri: 'file:///android_asset/stripeWebView/index.html'}
                : html
            }
            //Enable javascript, it also defaults to true only. 
            javaScriptEnabled
            // originWhitelist is important as the webpage hosted locally
            originWhitelist={['*']}
            // To avoid the flickering during render, RN Webview is powerful yet some glitches are there.
            style={{opacity: loading ? 0 : 1}}
            // Hook Reference to this component
            ref={webviewRef}
            // We are showing the loading progress while getting the webpage
            onLoadStart={() => {
              setLoadingText('Loading Secured Credit/Debit Card Input');
              setLoading(true);
            }}
            // We will send some data by injecting the javascript to webview
            injectedJavaScript={injectedJavascript}
            //after the load, we are sending the data like user name (Optional)
            onLoad={() => {
              webviewRef.current.postMessage('Hi');
              setLoading(false);
            }}
            // Required for android only
            domStorageEnabled
            // onMessage will give us the payment method id back to app
            onMessage={(e) => onMessage(e.nativeEvent.data)}/>
Enter fullscreen mode Exit fullscreen mode

The card elements in the Webview will look like in the image below. ( This is my design for demo. You can customise the CSS file in your way.)

Alt Text

Sometimes It will not look as above in ios devices. If that happens during your development, try to add CSS and JavaScript internally in the index.html file. It will work.

On clicking the save button, Stripe elements' CreatePaymentMethod function will create payment method and send the ID back to the app. In order to save the card and pay, we need some functions that will be running on the server-side.

I'll share Node.js snippets (For other languages guide, please refer to official docs).

Install Stripe with npm:

npm i stripe --save
Enter fullscreen mode Exit fullscreen mode

or yarn:

yarn add stripe
Enter fullscreen mode Exit fullscreen mode

Initialize Stripe:

const stripe = require('stripe')(`${StripeSecretKey}`);
Enter fullscreen mode Exit fullscreen mode

Create Customer:

const customer = await stripe.customers.create(
{
  name : 'Pradeep',
  email: 'pradeepanna24@gmail.com'
});
Enter fullscreen mode Exit fullscreen mode

Check if the customer already exists:

const customers = await stripe.customers.list({
  limit: 1,
  email: 'pradeepanna24@gmail.com'
});
Enter fullscreen mode Exit fullscreen mode

Save a card:

const paymentMethod = await stripe.paymentMethods.attach(
    `${Payment_Method_ID}`,
    {customer: `${Customer_ID}`});
Enter fullscreen mode Exit fullscreen mode

Delete a card:

const paymentMethod = await stripe.paymentMethods.detach(
  `${Payment_Method_ID}`
);
Enter fullscreen mode Exit fullscreen mode

Get saved cards:

const paymentMethods = await stripe.paymentMethods.list({
  customer: `${Customer_ID}`,
  type: 'card',
  limit: 2, //Optional
});
Enter fullscreen mode Exit fullscreen mode

Charge the card in the Payment Intent:

const paymentIntent = await stripe.paymentIntents.create({
  amount: 10000, // $100
  currency: 'cad',
  payment_method_types: ['card'],
  payment_method: payment_method_id,
  customer: customer_id, 
  charge : true, 
});
Enter fullscreen mode Exit fullscreen mode

Wrap the stripe APIs and call the appropriate ones from the client-side with Axios or any other module.

Note:

While saving the card, by default Stripe will accept the duplicate card number (i.e., it will accept the same card number more than once). It is actually a bug in the Stripe side still not resolved. This is not the real case scenario as no two people will have the same card number. But we have to take care of that in the accidental entry.

To check duplicate card, get the fingerprint of the card after saving and check if matches with already saved cards. If matches, delete the new card. You can do that in the client/server-side. I'll share the client-side code.

 const checkDuplicate = (fingerprint) => {
    /*
     Get the saved cards with API. check the fingerprint of saved cards matches with the card user saved now. If matches, delete the new card. The fingerprint of the card will not come in the payment method creation, it will be added after the attachment only.
   */
    let fil_cards = savedcards.map((e) => e.card.fingerprint === fingerprint);
    // if fill_cards[0]===true
    if (fil_cards[0]) {
     //delete the card
      Alert.alert(
        'Duplicate Card',
        'You have a saved card with this number. Please add different card.',
      );
    } else {
      // Do other activities
    }
  };
Enter fullscreen mode Exit fullscreen mode

You can check your customer creation, customer's payment methods and transaction details in your dashboard. In this guide, I used the stripe elements with the help of React Native Webview. Hope this will help who want to use Stripe card payment in React Native App with stripe elements.

As there is no official support from Stripe for React Native and they aren't backing Tipsi-Stripe too, You can use Native Modules or Webview to use stripe elements. I went with the later.

That's it

Thank you for scrolling to the bottom ( I believe you read too ). Please comment on your doubts/critics down below. I'll correct if something is not in good practice. I appreciate it if the blog helps you. :-)

Discussion (0)

pic
Editor guide