HELP: SCA - migrating SQPaymentForm to Web Payments SDK - PHP

Hi all,

Working through the migration from SqPaymentForm.

I need to implement SCA, which I have working to the point that the sandbox test cards show the modal correctly.

I enter the usual 123456 verification code, receive a verification token, and pass that to my createPayment().

I’m still getting the “CARD_DECLINED_VERIFICATION_REQUIRED” error regardless though.

My submt handler:

window.paymentFlowMessageEl.innerText = '';
	document.getElementById('card-button').disabled = true;
    try {
		const result = await card.tokenize();
		let verificationToken;
		
		verificationToken = await verifyBuyerHelper(result.token); 
      
        window.createPayment(result.token, verificationToken);

    } catch (e) {......

My ‘create payment’ function (partial)

   
   if (verificationToken !== undefined) {
          bodyParameters.verificationToken = verificationToken;
	}

	const dataJsonString = JSON.stringify(bodyParameters);
	
	console.log(dataJsonString);
	
	try {
		const response = await fetch('process-payment.php', {
			method: 'POST',
			headers: {
			'Content-Type': 'application/json'
			},
			body: dataJsonString
	});
	
    const data = await response.json();

The dataJsonString being shown in the console is:

[Log] {"token":"cnon:CBASEDdsdSbZ9MvIzk11EGDkFLA","familyName":"Jamieson","givenName":"Ben","address1":"","city":"","country":"GB","email":"","amount":"250","verificationToken":"verf:CBASENWAI7B36IheMuWVk6k7m2k"} 

This seems to indicate the verification token is being sent correctly (following all the GitHub sample apps)

Any clues as to why it would walk through the verification, then decline due to needing to be verified?

Ben

:wave: According to your API Logs the verification token verf:CBASENWAI7B36IheMuWVk6k7m2k you generated never got sent in the CreatePayment request. :slightly_smiling_face:

Hi @Bryan-Square ,

Thanks for the reply.

You can see above the ‘dataJsonString’ contents, showing there is a value for verificiationToken being submitted to the process-payment.php

How do I pass that to the CreatePaymentRequest which I currently am doing with:

 $create_payment_request = new CreatePaymentRequest($token, Uuid::uuid4(), $money);
  
  $response = $payments_api->createPayment($create_payment_request);

Again, as per your GitHub samples.

Am I missing something? If so, what and where?

That $token is the source_id that’s being passed to the payment request which is the cnon:CBASEDdsdSbZ9MvIzk11EGDkFLA. We see that in the request however the actual verification_token verf:CBASENWAI7B36IheMuWVk6k7m2k isn’t in the payment request.

Hi @Bryan-Square

Thanks for this pointer - will amend and see if I can get things going through.

Next question - how, after the transaction is complete, do I load a ‘confirmation’ page, passing the details of the entire form to it? This was trivial in the SqPaymentForm method, but I have no idea how to achieve it with this alternate method. It’s critical I can display the info to the client (and generate an email receipt)

Hi @Bryan-Square

Been searching, but I have no idea how to access the verification token in my process-payments.php, where the createPayment call is run.

It doesn’t appear to be being passed in the

file_get_contents('php://input')

… or if it is, I have no idea what it’s coming through as?

$data->verificationToken gives nothing

So how do I retrieve it?

Ben

@Bryan-Square

Just getting nowhere with this.

sq-payment-flow.js contains:

const response = await fetch('process-payment.php', {
			method: 'POST',
			headers: {
			'Content-Type': 'application/json'
			},
			body: dataJsonString
	});
	
    const data = await response.json();

process-payment.php contains:

// all puled from https://github.com/square/connect-api-examples/tree/master/connect-examples/v2/php_payment

$payments_api = $square_client->getPaymentsApi();

$money = new Money();
$money->setAmount($theamount);
$money->setCurrency($location_info->getCurrency());

// these values set correctly

try {

// These Two Lines work perfectly, but don't include SCA
  //$create_payment_request = new CreatePaymentRequest($token, Uuid::uuid4(), $money);
  //$response = $payments_api->createPayment($create_payment_request);

// Trying to send token & Verification token - the verification token pulling through correctly.
  $response = $payments_api->createPayment($token,$vt);

This results in the less than useful error of:

[Error] Error: – SyntaxError: The string did not match the expected pattern. — sq-payment-flow.js:56

What string? What is the expected pattern?

Losing days on this - please help!!

Ben

Any assistance on this @Bryan-Square?

Even your own documentation says “TODO: add verification”

Its like even Square doesn’t know how to get this working!

From: https://developer.squareup.com/docs/web-payments/sca

async function handlePaymentMethodSubmission(
  event,
  paymentMethod,
  shouldVerify = false
) {
  event.preventDefault();

  try {
    // disable the submit button as we await tokenization and make a payment
    // request.
    cardButton.disabled = true;
    const token = await tokenize(paymentMethod);
    let verificationToken;

    if (shouldVerify) {
      verificationToken = await verifyBuyer(
        payments,
        token
      );
    }

    console.debug('Verification Token:', verificationToken);

    //TODO: Add the verification token in Step 2.2
    const paymentResults = await createPayment(token);
    displayPaymentResults('SUCCESS');

    console.debug('Payment Success', paymentResults);
  } catch (e) {
    cardButton.disabled = false;
    displayPaymentResults('FAILURE');
    console.error(e.message);
  }
}

When will the TODO be TODONE???

I’ve got the token, but there appears to be no documented way to pass it to the

CreatePaymentRequest

Class, which is kinda critical

When you print $data is the verificationToken available?

Yes:

[17-Mar-2022 13:16:40 America/New_York] stdClass Object
(
    [token] => cnon:CBASEP7WHHtljAaUPUFmuLp6VCI
    [amount] => 9102
    [verificationToken] => verf:CBASEDPvwdNjetuZDAUfq4gQn2k
)

But how do I pass it to

CreatePaymentRequest

I’ve tried:

$create_payment_request = new CreatePaymentRequest($token, Uuid::uuid4(), $money, $vt);

Nope.

This:

 $response = $payments_api->createPayment($create_payment_request, $vt);

Nope.

this:

$response = $payments_api->createPayment($token,$vt);

Also a huge bag of Nope.

And absolutely no documentation to ptovide any insight into exactly how to do it.

Give me a clue? PLEASE!!!

Hi @Bryan-Square

Any additional thoughts on where this is going wrong?

@Bryan-Square

Is this just being ignored now? Is there anywhere else I can ask for assistance?

Resolved.

While completely missing from all documentation and all code samples provided by Square relating to migrating, I needed to add

$create_payment_request->setVerificationToken($verificationToken);

after the

new CreatePaymentRequest()

call.

Glad to hear you got it to work! :slightly_smiling_face:

Ha.

Absolutely no thanks to Squares official ‘documentation’ or non-functional code samples.

Lost days on this.

I just found this article. I’m using the web quick start for php, and even though it does verification, the process-payment.php still comes back with failed payment. I’ve attempted the fix above.

Here’s my working code, which does make an API call, but regardless payment fails.

<?php

// Note this line needs to change if you don't use Composer:
// require('square-php-sdk/autoload.php');
require 'vendor/autoload.php';
require('environment.php');
include 'utils/location-info.php';

use Square\SquareClient;
use Square\Environment;
use Square\Models\Money;
use Square\Models\CreatePaymentRequest;
use Square\Exceptions\ApiException;
use Ramsey\Uuid\Uuid;

if ($_SERVER['REQUEST_METHOD'] != 'POST') {
  error_log('Received a non-POST request');
  echo 'Request not allowed';
  http_response_code(405);
  return;
}

$json = file_get_contents('php://input');
$data = json_decode($json);
$token = $data->token;

$square_client = new SquareClient([
  'accessToken' => getenv('SQUARE_ACCESS_TOKEN'),
  'environment' => Environment::CUSTOM,
  'customUrl' => getenv('c_url')
]);

$payments_api = $square_client->getPaymentsApi();

// To learn more about splitting payments with additional recipients,
// see the Payments API documentation on our [developer site]
// (https://developer.squareup.com/docs/payments-api/overview).

$money = new Money();
// Monetary amounts are specified in the smallest unit of the applicable currency.
// This amount is in cents. It's also hard-coded for $1.00, which isn't very useful.
$money->setAmount(100);
// Set currency to the currency for the location
$money->setCurrency($location_info->getCurrency());

try {
// Every payment you process with the SDK must have a unique idempotency key.
// If you're unsure whether a particular payment succeeded, you can reattempt
// it with the same idempotency key without worrying about double charging
// the buyer.
  $create_payment_request = new CreatePaymentRequest($token, Uuid::uuid4(), $money);
  $create_payment_request->setVerificationToken($verificationToken);

  $response = $payments_api->createPayment($create_payment_request);

  if ($response->isSuccess()) {
    echo json_encode($response->getResult());
  } else {
    echo json_encode(array('errors' => $response->getErrors()));
  }
} catch (ApiException $e) {
  echo json_encode(array('errors' => $e));
}

:wave: What was the decline message from the payment or do you have the payment_id?

It was from the payment form directly. When I click submit, it generates the nonce, but either way, even without SCA, it ends up doing Payment Failed. I have the SDK installed, but this is what I was using… connect-api-examples/connect-examples/v2/php_payment at master · square/connect-api-examples · GitHub

Sandbox App ID is sandbox-sq0idb-iCCXoi9_2nolNBkH2TJRIg Also since I’m a new user, i’m limited to 3 posts.

Update 625 pm et: My Current Script calls Verify buyer. I’m able to get the SCA Window, but it appears somehow it’s not passing it on to preocess-payment.php . Can I somehow get around this 3 reply limit so I don’t have to keep updating this?

This is what my form looks like

<!DOCTYPE html>
<html lang="en">
  <head>
   <meta charset=UTF-8>
    <link href="assets/style.php" rel="stylesheet" />
	<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
    <script>
      const appId = 'sandbox-sq0idb-iCCXoi9_2nolNBkH2TJRIg';
      const locationId = '442SK8P2Z1YAH';

        async function initializeCard(payments) {
        const card = await payments.card();
        await card.attach('#card-container');

        return card;
      }

      // verificationToken can be undefined, as it does not apply to all payment methods.
      async function createPayment(token, verificationToken) {
        const bodyParameters = {
          locationId,
          sourceId: token,
        };

        if (verificationToken !== undefined) {
          bodyParameters.verificationToken = verificationToken;
        }

        const body = JSON.stringify(bodyParameters);

        const paymentResponse = await fetch('https://dev.bestphpscripts.com/sqtest/process-payment.php', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
		   body: dataJsonString,
          //body,
        });

        if (paymentResponse.ok) {
          return paymentResponse.json();
        }

        const errorBody = await paymentResponse.text();
        throw new Error(errorBody);
      }

      async function tokenize(paymentMethod) {
        const tokenResult = await paymentMethod.tokenize();
        if (tokenResult.status === 'OK') {
          return tokenResult.token;
        } else {
          throw new Error(
            `Tokenization errors: ${JSON.stringify(tokenResult.errors)}`
          );
        }
      }

      // status is either SUCCESS or FAILURE;
      function displayPaymentResults(status) {
        const statusContainer = document.getElementById(
          'payment-status-container'
        );
        if (status === 'SUCCESS') {
          statusContainer.classList.remove('is-failure');
          statusContainer.classList.add('is-success');
        } else {
          statusContainer.classList.remove('is-success');
          statusContainer.classList.add('is-failure');
        }

        statusContainer.style.visibility = 'visible';
      }

      async function verifyBuyer(payments, token) {
        const verificationDetails = {
          amount: '1.00',
          billingContact: {
            addressLines: ['123 Main Street', 'Apartment 1'],
            familyName: 'Doe',
            givenName: 'John',
            email: '[email protected]',
            country: 'US',
            phone: '3214563987',
            region: 'FL',
            city: 'Sample City',
          },
          currencyCode: 'USD',
          intent: 'CHARGE',
        };

        const verificationResults = await payments.verifyBuyer(
          token,
          verificationDetails
        );
        return verificationResults.token;
      }

      document.addEventListener('DOMContentLoaded', async function () {
        if (!window.Square) {
          throw new Error('Square.js failed to load properly');
        }

        let payments;
        try {
          payments = window.Square.payments(appId, locationId);
        } catch {
          const statusContainer = document.getElementById(
            'payment-status-container'
          );
          statusContainer.className = 'missing-credentials';
          statusContainer.style.visibility = 'visible';
          return;
        }

        let card;
        try {
          card = await initializeCard(payments);
        } catch (e) {
          console.error('Initializing Card failed', e);
          return;
        }

        async function handlePaymentMethodSubmission(
          event,
          paymentMethod,
          shouldVerify = false
        ) {
          event.preventDefault();

          try {
            // disable the submit button as we await tokenization and make a payment request.
            cardButton.disabled = true;
            const token = await tokenize(paymentMethod);
            let verificationToken;

            if (shouldVerify) {
              verificationToken = await verifyBuyer(payments, token);
            }

            const paymentResults = await createPayment(
              token,
              verificationToken
            );

            displayPaymentResults('SUCCESS');
            console.debug('Payment Success', paymentResults);
          } catch (e) {
            cardButton.disabled = false;
            displayPaymentResults('FAILURE');
            console.error(e.message);
          }
        }

        const cardButton = document.getElementById('card-button');
        cardButton.addEventListener('click', async function (event) {
          // SCA only needs to be run for Card Payments. All other payment methods can be set to false.
          handlePaymentMethodSubmission(event, card, true);
        });
      });
    </script>
  </head>
  <body>
    <form id="payment-form" method="POST" action="process-payment.php">
      <div id="afterpay-button"></div>
      <div id="card-container"></div>
      <button id="card-button" type="button">Pay $1.00</button>
    </form>
    <div id="payment-status-container"></div>
  </body>
</html>

What’s your application ID?