Payment Form via Netlify Functions

I’m trying to implement the Payment Form in a website that I have built and am hosting via Netlify.

I have followed the walkthrough successfully outside of Netlify but when I try to move the content from the walkthrough onto my Netlify site I am running into 404 errors hitting the payment function.

I have modified the site code or the script to move the server.js file into the /functions/ folder that Netlify requires to make the script available and tried to update the paths to match. I have also added serverless-http per Netlify’s instructions on hosting express.js apps via Netlify functions. Here is the content of my server.js file:

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);

This it the payment form code I am using.

<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 explain why I am seeing a 404 error? I have confirmed that Netlify is deploying the server.js file so it should be available but I can’t figure out how to tell the payment form where the script is located.

I think you’re missing the server in your path. Currently, Netlify appears to be routing the request to a file named process-payment instead of a file named server with a sub-path of /process-payment (since that is what the filename of your Express application is).

I’m not super familiar with Netlify, but it seems the routing goes https://{BASE_URL}/.netlify/functions/{FUNCTION_FILE_NAME} and then whatever file is at FUNCTION_FILE_NAME would handle the incoming request.

Looking at the example repo here, it seems to follow that pattern.

The other repo linked in that blog post also follows a similar pattern, but it is slightly harder to tell.

1 Like