Square Payment Form with Netlify Functions

Site name: lucid-brattain-8cb631

The content in question is available on a branch called sqpaymentform.

I am trying to implement Square’s Payment Form on a website that I have built and deployed using Jekyll and Netlify. I have successfully followed Square’s walkthrough to build the payment form locally using node.js.

When I add the server.js script as a Netlify function, the site will build successfully but when I try to use the payment form to hit the server.js function, I get a 404 error. The only changes I have made to the files provided by Square are updating the path to the function to match Netlify’s function endpoints (line 85 in the payment form and line 36 in server.js). I have also added serverless-http per the Netlify instructions for hosting express.js apps in Netlify Functions.

server.js

const express = require('express');
const serverless = require('serverless-http');
const bodyParser = require('body-parser');
const { Client, Environment, ApiError } = require('square');

const app = express();
const port = 3000;

// Set the Access Token which is used to authorize to a merchant
const accessToken = 'REMOVED_ACCESS_TOKEN';

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(__dirname));

// Initialized the Square api client:
//   Set sandbox environment for testing purpose
//   Set access token
const client = new Client({
  environment: Environment.Sandbox,
  accessToken: accessToken,
});

app.post('.netlify/functions/process-payment', async (req, res) => {
  const requestParams = req.body;

  // Charge the customer's card
  const paymentsApi = client.paymentsApi;
  const requestBody = {
    sourceId: requestParams.nonce,
    amountMoney: {
      amount: 100, // $1.00 charge
      currency: 'USD'
    },
    locationId: requestParams.location_id,
    idempotencyKey: requestParams.idempotency_key,
  };

  try {
    const response = await paymentsApi.createPayment(requestBody);
    res.status(200).json({
      'title': 'Payment Successful',
      'result': response.result
    });
  } catch(error) {
    let errorResult = null;
    if (error instanceof ApiError) {
      errorResult = error.errors;
    } else {
      errorResult = error;
    }
    res.status(500).json({
      'title': 'Payment Failure',
      'result': errorResult
    });
  }
});

app.listen(
  port,
  () => console.log(`listening on - http://localhost:${port}`)
);

module.exports.handler = serverless(app);

payment-form.html

<div id="form-container">
      <div id="sq-card-number"></div>
      <div class="third" id="sq-expiration-date"></div>
      <div class="third" id="sq-cvv"></div>
      <div class="third" id="sq-postal-code"></div>
      <button id="sq-creditcard" class="button-credit-card" onclick="onGetCardNonce(event)">Pay $1.00</button>
    </div> <!-- end #form-container -->
    <script type="text/javascript">

      //TODO: paste code from step 2.1.1
      const idempotency_key = uuidv4();

      // Create and initialize a payment form object
      const paymentForm = new SqPaymentForm({
        // Initialize the payment form elements

        //TODO: Replace with your sandbox application ID
        applicationId: "sandbox-sq0idb-MYmeor4LAw5C0tBRgJ0uBQ",
        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;
            }
            //TODO: Replace alert with code in step 2.1
            fetch('/.netlify/functions/process-payment', {
              method: 'POST',
              headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                nonce: nonce,
                idempotency_key: idempotency_key,
                location_id: "LVEKPR22VWSXS"
              })
            })
            .catch(err => {
              alert('Network error: ' + err);
            })
            .then(response => {
              if (!response.ok) {
                return response.json().then(
                  errorInfo => Promise.reject(errorInfo));
              }
              return response.json();
            })
            .then(data => {
              console.log(data);
              alert('Payment complete successfully!\nCheck browser developer console for more details');
            })
            .catch(err => {
              console.error(err);
              alert('Payment failed to complete!\nCheck browser developer console for more details');
            });
         }
       }
     });
     //TODO: paste code from step 1.1.4
     //TODO: paste code from step 1.1.5
     paymentForm.build();

     //TODO: paste code from step 2.1.2
     //Generate a random UUID as an idempotency key for the payment request
     // length of idempotency_key should be less than 45
     function uuidv4() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
          var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
          return v.toString(16);
        });
     }



     // onGetCardNonce is triggered when the "Pay $1.00" button is clicked
     function onGetCardNonce(event) {

       // Don't submit the form until SqPaymentForm returns with a nonce
       event.preventDefault();
       // Request a nonce from the SqPaymentForm object
       paymentForm.requestCardNonce();
     }
    </script>

Can anyone help me figure out why I am getting a 404 error and how to update the script / payment form so that I can access this function via Netlify? Thanks!

Edit: the included API keys are all sandbox keys, by the way.

hey there! sorry to be slow to get back to you, this kind of thing is not something we usually get much of a chance to debug :expressionless:

have you been able to make any progress on this?

Is there any chance the form is being transmitted via POST? that - won’t work.

https://answers.netlify.com/search?expanded=true&q=form%20post

No worries on the delay - I’ve figured out a short term workaround.

The form is using POST, yes. When you say “that won’t work” do you mean that it won’t work locally or that Netlify functions don’t support POST requests?

Hey @fetamorphasis,
I’m not seeing that any functions have been uploaded to that site: Netlify App

Your most recent deploy shows this no-op in your logs: Netlify App

The Netlify Functions setting targets a non-existing directory: functions

I’m also pretty sure that your app.listen line will not work within a Netlify Function.

Do you want to try updating your functions directory in your netlify.toml and see if that works better?