Square credit card payment integration using React and React Hooks
Square Payment Gateway
If you are reading this post then you already know square is a payment gateway that accepts payment. In this article, I will explain how you can integrate square payment using react.
Things you should do to understand this post.
- Learn the Basics of React and Node.
- Sign up to the square, create a sandbox account and make a note of application Id and access token of the application.
- Read the official doc which explains how you can integrate square using basic HTML, CSS, and JavaScript, this post focuses more on converting the same code into a React.
Steps
- Run below mentioned and command and test your app.
npx create-react-app client cd client npm start
2. Remove unnecessary codes and files like React Logo.
3. Inside client folder create .env file and add application id.
REACT_APP_APLLICATION_ID=Application Id
4. Add CSS code in app.css to design payment form.
button { border: 0; font-weight: 500; } fieldset { margin: 0; padding: 0; border: 0; } #form-container { position: relative; width: 380px; margin: 0 auto; top: 50%; } .third { float: left; width: calc((100% - 32px) / 3); padding: 0; margin: 0 16px 16px 0; } .third:last-of-type { margin-right: 0; } /* Define how SqPaymentForm iframes should look */ .sq-input { height: 56px; box-sizing: border-box; border: 1px solid #E0E2E3; background-color: white; border-radius: 6px; display: inline-block; -webkit-transition: border-color .2s ease-in-out; -moz-transition: border-color .2s ease-in-out; -ms-transition: border-color .2s ease-in-out; transition: border-color .2s ease-in-out; } /* Define how SqPaymentForm iframes should look when they have focus */ .sq-input--focus { border: 1px solid #4A90E2; } /* Define how SqPaymentForm iframes should look when they contain invalid values */ .sq-input--error { border: 1px solid #E02F2F; } #sq-card-number { margin-bottom: 16px; } /* Customize the "Pay with Credit Card" button */ .button-credit-card { width: 100%; height: 56px; margin-top: 10px; background: #4A90E2; border-radius: 6px; cursor: pointer; display: block; color: #FFFFFF; font-size: 16px; line-height: 24px; font-weight: 700; letter-spacing: 0; text-align: center; -webkit-transition: background .2s ease-in-out; -moz-transition: background .2s ease-in-out; -ms-transition: background .2s ease-in-out; transition: background .2s ease-in-out; } .button-credit-card:hover { background-color: #4281CB; }
5. Load Payment Form Script inside app.js
import React, { useState, useEffect } from 'react'; import './App.css'; import Square from './Component/Square'; const App = () => { const [isLoad, setLoad] = useState(false); useEffect(() => { let sqPaymentScript = document.createElement("script"); // sandbox: https://js.squareupsandbox.com/v2/paymentform // production: https://js.squareup.com/v2/paymentform sqPaymentScript.src = "https://js.squareupsandbox.com/v2/paymentform"; sqPaymentScript.type = "text/javascript"; sqPaymentScript.async = false; sqPaymentScript.onload = () => { setLoad(true); }; document.getElementsByTagName("head")[0].appendChild(sqPaymentScript); }); const squarePayment = isLoad ? ( <Square paymentForm={ window.SqPaymentForm }/> ) : ( null ) return ( <div className="App"> <h1>Square</h1> {squarePayment} </div> ); } export default App;
As you can see in the code we are using plan javascript inside the useEffect hook which is similar to componentDidMount and componentDidUpdate to load the square payment form in <head>.
The child component Square is used for loading, formatting and managing the payment form and isLoad state is used to make sure the child component is render only when the script is loaded and we can pass SqPaymentForm object as a prop.
6. Add a file Utils/paymentForm.js to initialize payment form elements.
const config = { // Initialize the payment form elements //TODO: Replace with your sandbox application ID applicationId: process.env.REACT_APP_APLLICATION_ID, inputClass: 'sq-input', autoBuild: false, // Customize the CSS for SqPaymentForm iframe elements inputStyles: [{ fontSize: '16px', lineHeight: '24px', padding: '16px', placeholderColor: '#a0a0a0', backgroundColor: 'transparent', }], // Initialize the credit card placeholders cardNumber: { elementId: 'sq-card-number', placeholder: 'Card Number' }, cvv: { elementId: 'sq-cvv', placeholder: 'CVV' }, expirationDate: { elementId: 'sq-expiration-date', placeholder: 'MM/YY' }, postalCode: { elementId: 'sq-postal-code', placeholder: 'Postal' }, // SqPaymentForm callback functions callbacks: { /* * callback function: cardNonceResponseReceived * Triggered when: SqPaymentForm completes a card nonce request */ cardNonceResponseReceived: function (errors, nonce, cardData) { if (errors) { // Log errors from nonce generation to the browser developer console. console.error('Encountered errors:'); errors.forEach(function (error) { console.error(' ' + error.message); }); alert('Encountered errors, check browser developer console for more details'); return; } //alert(`The generated nonce is:\n${nonce}`); fetch('http://localhost:4000/process-payment', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ nonce: nonce }) }) .catch(err => { alert('Network error: ' + err); }) .then(response => { if (!response.ok) { return response.text().then(errorInfo => Promise.reject(errorInfo)); } return response.text(); }) .then(data => { console.log(JSON.stringify(data)); alert('Payment complete successfully!\nCheck browser developer console form more details'); }) .catch(err => { console.error(err); alert('Payment failed to complete!\nCheck browser developer console form more details'); }); } } } export default config;
7. Create a child component.
import React from 'react'; import config from '../Utils/paymentForm'; const Square = ({ paymentForm }) => { paymentForm = new paymentForm(config); paymentForm.build(); const requestCardNonce = () =>{ paymentForm.requestCardNonce(); } return ( <div id="form-container"> <div id="sq-card-number"></div> <div className="third" id="sq-expiration-date"></div> <div className="third" id="sq-cvv"></div> <div className="third" id="sq-postal-code"></div> <button id="sq-creditcard" className="button-credit-card" onClick={requestCardNonce}> Pay £ 1.00</button> </div> ) } export default Square;
8. The next step is to work on the backend, create a package.json and run npm install command to install all dependencies.
{ "name": "sqpaymentform-nodejs-starterkit", "version": "0.1.0", "private": true, "scripts": { "start": "nodemon server.js" }, "dependencies": { "body-parser": "^1.19.0", "express": "^4.17.0", "square-connect": "2.20190814.0" }, "devDependencies": { "nodemon": "^1.19.0" } }
9. Create .env on the backend and define an access token.
ACCESS_TOKEN=acess_token
10. The final step is to add server.js file.
const express = require('express'); const bodyParser = require('body-parser'); const crypto = require('crypto'); const squareConnect = require('square-connect'); const dotenv = require('dotenv'); dotenv.config(); const app = express(); const port = 4000; var cors = require('cors'); app.use(cors()) // Set the Access Token const accessToken = process.env.ACCESS_TOKEN app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(express.static(__dirname)); // Set Square Connect credentials and environment const defaultClient = squareConnect.ApiClient.instance; // Configure OAuth2 access token for authorization: oauth2 const oauth2 = defaultClient.authentications['oauth2']; oauth2.accessToken = accessToken; // Set 'basePath' to switch between sandbox env and production env // sandbox: https://connect.squareupsandbox.com // production: https://connect.squareup.com defaultClient.basePath = 'https://connect.squareupsandbox.com'; app.post('/process-payment', async (req, res) => { const request_params = req.body; // length of idempotency_key should be less than 45 const idempotency_key = crypto.randomBytes(22).toString('hex'); // Charge the customer's card const payments_api = new squareConnect.PaymentsApi(); const request_body = { source_id: request_params.nonce, amount_money: { amount: 100, // £1.00 charge currency: 'GBP' }, idempotency_key: idempotency_key }; try { const response = await payments_api.createPayment(request_body); res.status(200).json({ 'title': 'Payment Successful', 'result': response }); } catch(error) { res.status(500).json({ 'title': 'Payment Failure', 'result': error.response.text }); } }); app.listen( port, () => console.log(`listening on - http://localhost:${port}`) );
once all steps are done, run both react and node servers to test the functionality.
Test Credentials
Card Number: 4111 1111 1111 1111, CVV: 111, Postal: 11111 (you can provide any postal code).
Launch your app in sandbox mode to see the transaction.
Payment Form