Some card transactions failing that really shouldn't!

hi guys,

Pulling my hair out at little!.. I implemented a new checkout recently onto my website, with the help of a square support rep.

I tried this with 3 different bank cards today myself and all were successful - however multiple clients of mine are trying to pay, and the payments are declining.

Josh at square support said

“I took a look at your logs, and I’m seeing authorization errors that indicate that the card was decline due to lack of verification.”

I am already asking for “first name”, “last name”, “email”, “phone number”, “city” as part of verification as well as the square form asking for card details but it still declines. This is not a case of the customer putting incorrect card details in as I have checked personally with one and watched them put their information in.

Can anyone take a quick look and maybe point me in the right direction?

Thanks

– Checkout Form

<input type="hidden" id="orderValue" value="<?php echo $price['rawPrice'] / 100; ?>">
<?php 
$_SESSION['orderValue'] = $price['rawPrice'];
?>
<Table border="0">
    <tr>
        <td>
          Card-payer First Name:   
        </td>
        <td>
            <input type="text" id="fName">
        </td>
    </tr>
        <tr>
        <td>
            Card-payer Last Name: 
        </td>
        <td>
            <input type="text" id="lName">
        </td>
    </tr>
        <tr>
        <td>
            Address Line 1:
        </td>
        <td>
            <input type="text" id="addressone">
        </td>
    </tr>
        <tr>
        <td>
            Address Line 2: 
        </td>
        <td>
            <input type="text" id="addresstwo">
        </td>
    </tr>
           <tr>
        <td>
            City: 
        </td>
        <td>
            <input type="text" id="city">
        </td>
    </tr>
        </tr>
        <tr>
        <td>
            Phone Number:
        </td>
        <td>
            <input type="text" id="phone">
        </td>
    </tr>
        </tr>
        </tr>
        <tr>
        <td>
            E-mail Address:
        </td>
        <td>
            <input type="text" id="email">
        </td>
    </tr>
    
</table>
<h3>Card Details</h3>
<form id="payment-form">
      <div id="card-container"></div>
      <button id="card-button" type="button">Pay</button>
    </form>
    <div id="paymentProcessing" style="display: none;">
                  <span style="color: #02198B">Transaction in progress, Please wait...</span>
    </div>
    <div id="paymentComplete" style="display: none;">
          <span style="color: #0A0">Transaction Complete, Redirecting to summary.</span>
    </div>
    <div id="paymentDeclined" style="display: none;">
        <span style="color: #ff0000">Transaction Declined </span>
    </div>
    <div id="payment-status-container"></div>

Javascript to handle payment:

      <script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>

  <script>
                const appId = "removed for security";
                const locationId = "removed for security";
                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('process_card.php', {
                method: 'POST',
                        headers: {
                        'Content-Type': 'application/json',
                        },
                        body,
                });
                const data = await paymentResponse.json();
                if (data.errors && data.errors.length > 0) {
                const errorBody = await paymentResponse.text();
                throw new Error(errorBody);
                } else{
                return data;
                }




                }

                async function tokenize(paymentMethod) {
                const tokenResult = await paymentMethod.tokenize();
                if (tokenResult.status === 'OK') {
                console.log("Token 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') {
                displayControl("paymentProcessing", false);
                displayControl("paymentComplete", true);
                displayControl("paymentDeclined", false);
                statusContainer.classList.remove('is-failure');
                statusContainer.classList.add('is-success');
                window.location = "orderDetails.php?orderIdBySession=true&status=finished";
                } else {
                displayControl("paymentProcessing", false);
                displayControl("paymentComplete", false);
                displayControl("paymentDeclined", true);
                statusContainer.classList.remove('is-success');
                statusContainer.classList.add('is-failure');
                }

                statusContainer.style.visibility = 'visible';
                }

                async function verifyBuyer(payments, token) {
                var firstNameGiven = getDomValue("fName");
                var lastNameGiven = getDomValue("lName");
                var addressFirstLine = getDomValue("addressone");
                var addressSecondLine = getDomValue("addresstwo");
                var city = getDomValue("city");
                var email = getDomValue("email");
                var phone = getDomValue("phone");
                var rawprice = getDomValue("orderValue");
// getDomValue is from a separate JS script, it does work and an alert() returns the values.
                const verificationDetails = {
                amount: rawprice,
                        billingContact: {
                        addressLines: [addressFirstLine, addressSecondLine],
                                familyName: lastNameGiven,
                                givenName: firstNameGiven,
                                email: email,
                                phone: phone,
                                city: city
                        },
                        currencyCode: 'GBP',
                        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.
                displayControl("paymentProcessing", true);
                displayControl("paymentComplete", false);
                displayControl("paymentDeclined", false);
                cardButton.disabled = true;
                const token = await tokenize(paymentMethod);
                console.log('token created');
                let verificationToken;
                if (shouldVerify) {
                console.log('shouldVerify');
                verificationToken = await verifyBuyer(payments, token);
                console.log('after await');
                }

                const paymentResults = await createPayment(
                        token,
                        verificationToken
                        );
                displayPaymentResults('SUCCESS');
                } 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>

And the Php processing script

<?php

session_start();
// Note this line needs to change if you don't use Composer:
require 'vendor/autoload.php';
$squareupAccessToken = $_SESSION['squareupAccessToken'];
$environment = $_SESSION['environment'];

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

// dotenv is used to read from the '.env' file created for credentials
//$dotenv = Dotenv::create(__DIR__);
//$dotenv->load();

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->sourceId;
$square_client = new SquareClient([
    'accessToken' => $squareupAccessToken,
    'environment' => $environment
        ]);

$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($_SESSION['orderValue']);
// Set currency to the currency for the location
$money->setCurrency("GBP");

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.
    $orderRef = Uuid::uuid4();
    $create_payment_request = new CreatePaymentRequest($token, $orderRef, $money);

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

    if ($response->isSuccess()) {
        $json = json_encode($response->getResult());

        //****START OF CODE TO RECORD THE TRANSCATION ON SYSTEM****
        $forceHTTPS = true;
        require ("includes/globalstart.php");
        $exceptionMade = false;

        $orderReference = $orderRef;
        $cartSession = $_SESSION['cartSession'];

        $orderId = $_shop->createNewOrderFromCart($_SESSION['studentPortalStudentId'], $cartSession, $orderReference, $_SESSION['fulfillingInstructor']);
        $_SESSION['orderIdBySession'] = $orderId;
        $_shop->appendSquareupTransactionJsonDataToOrder($orderId, $json);
        $_shop->setCartSessionVouchersToUsed($cartSession, $orderId);

        $_shop->resetCart($_SESSION['studentPortalStudentId']);


        unset($_SESSION['cartSession']);
        if (!$exceptionMade) {
            //e-mail order summary to coach and client
            $esmsAccountId = $_SESSION['esmsAccountId'];
            $InstructorEmailTo = $encd->encodeData($_SESSION['emailOrderSummaryTo']);
            $getMember = $_member->findStudentv2($_SESSION['studentPortalStudentId']);
            $studentEmailAddress = $encd->encodeData($getMember['email']);
            $getOrgName = $cdb->select("Accounts", "AccountId", $esmsAccountId);
            while ($r = mysqli_fetch_array($getOrgName)) {
                $Orgname = $r['AccountName'];
            }
            $Instructorsubject = $Orgname . " - New Online Shop Order #" . str_pad($orderId, 6, "0", STR_PAD_LEFT);
            $Studentsubject = "Your " . $Orgname . " order #" . str_pad($orderId, 6, "0", STR_PAD_LEFT);
            $InstructorListReference = "Shop Order Instructor Copy - order " . str_pad($orderId, 6, "0", STR_PAD_LEFT);
            $StudentListReference = "Shop Order Student Copy - order " . str_pad($orderId, 6, "0", STR_PAD_LEFT);
            if ($_SERVER['REMOTE_ADDR'] == "127.0.0.1") {
                $webdir = "http://" . $_SERVER['SERVER_NAME'] . dirname($_SERVER['PHP_SELF']);
            } else {
                $webdir = "https://" . $_SERVER['SERVER_NAME'] . dirname($_SERVER['PHP_SELF']);
            }
            $body = curl_get_contents($webdir . '/plainOrderFile.php?esmieId=' . $_SESSION['esmsId'] . '&securityKey=!tjVJ^9c!fznaBnAq2mDZ=AYdU25?K7Xkxygf@vNVJV3-$6HYL&pass=6uWt7$@fGfuj9_DMUuDtUqzy=qWFctHj-9P-BvGRwbGYu^B?L&orderId=' . $orderId);
            $emailHtml = htmlspecialchars($body, ENT_QUOTES);
            $Instructorsubject = htmlspecialchars($Instructorsubject, ENT_QUOTES);
            $Studentsubject = htmlspecialchars($Studentsubject, ENT_QUOTES);

            $cdb->query("INSERT INTO `emailManagement` (`emailId`, `ESMSAccountID`, `emailTo`, `subject`, `body`, `ListReference`, `sendStatus`, `timestamp`)VALUES (NULL, '$esmsAccountId', '$InstructorEmailTo', '$Instructorsubject', '$emailHtml', '$InstructorListReference', '0', NULL)");
            $cdb->query("INSERT INTO `emailManagement` (`emailId`, `ESMSAccountID`, `emailTo`, `subject`, `body`, `ListReference`, `sendStatus`, `timestamp`)VALUES (NULL, '$esmsAccountId', '$studentEmailAddress', '$Studentsubject', '$emailHtml', '$StudentListReference', '0', NULL)");

        }

//****END OF INJECTED CODE****

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

The checkout form;

Thanks in advance for anyone that can advise

:wave: Do you have a recent payment_id that failed so we can take a look? :slightly_smiling_face:

Hi Bryan,

After an e-mail back this morning, I did a bit of digging… it looks like I get a verifybuyer token on the JS side - but it doesn’t look like I do anything with it at all in the process-card.php file.

What would I need to add in the process-card.php file to make sure the verifybuyer token is passed into the payment request?

Thanks
Nathan

Glad to hear you figured it out. You’ll want to pass it to the backend like you do with the source_id and include it in the CreatePayment request. :slightly_smiling_face:

Hi,

Not sure if it’s me being thick but…

ON the php backend, I have added this;

$verificationToken = null;
if ($data->verificationToken){
  $verificationToken = $data->verificationToken;  
}

Looking at the code for the createPaymentRequest code, would I do the following next?

    $create_payment_request = new CreatePaymentRequest($token, $orderRef, $money);
    $create_payment_request->setVerificationToken($verificationToken);

Please advise if I have done this correctly, and the verfiication token will now submit with the payment?

Thanks
Nathan Webb

I just took a look at your logs and I see that the verification token is being passed in the payment request. So yes it’s working! :slightly_smiling_face:

Hi, I have the same issue. Could you check my payment.id:

3bsu2ZHYeMDF499WsE5QTgIqOTQZY

I’m currently confused about how to handle this error because my customer has filled in the billing contact data and the error is still the same, namely CARD_DECLINED_VERIFICATION_REQUIRED. A popup for verification should appear but it never happened. please give advice

I see that a verification_token was included in the request. This error can still happen if the incorrect information was used to when generating the verification_token. If your customer is confident that the information that was used to generate the verification_token was correct they should contact their bank. :slightly_smiling_face:

Okay thank you for your information

Hi @Bryan-Square , is there a certain format for input billingContact? whether it is in the form of free text or must input the code as well as the region. I see in the region contains the code. and for addressLines there are two strings, does that mean there are two addresses or what? I’m worried that the error that occurs is due to an input format error which results in no pop up for verification.

and I see in the documentation it seems out of sync between the images below. can you give me a specific explanation or example if I want to make a billingContact input form. because I don’t see an example for this on github

Hi @Bryan-Square , is there a certain format for input billingContact? whether it is in the form of free text or must input the code as well as the region. I see in the region contains the code. and for addressLines there are two strings, does that mean there are two addresses or what? I’m worried that the error that occurs is due to an input format error which results in no pop up for verification

if you see this image, you can see that on the left side using “state” object for state/province/region name. but on the right side using “region” object for “LND”, i think that it’s a region code or something. what do you think? I hope you give me specific format for each billingCode data. Thank you so much