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