Fixes this, ==> currently the order token is passed as a callback param just in base64 encoded format which is not protected, an attacker can decode the token from the url to make a call to the api to modify the order status
179 lines
7.5 KiB
PHP
179 lines
7.5 KiB
PHP
<?php
|
|
|
|
$client_secret = "lfeKILJMFQVc3vXzW79B6TI5VKs8DFeT"; // This is a dummy value. Place your client_secret key here. You received it from Ecwid team in email when registering the app
|
|
//$cipher = "AES-128-CBC";
|
|
$iv = "abcdefghijklmnopqrstuvwx";// this can be generated random if you plan to store it for later but in this case e.g. openssl_random_pseudo_bytes($ivlen);
|
|
$cipher = "aes-128-gcm";
|
|
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
|
|
$tag = 0;
|
|
|
|
// If this is a payment request
|
|
|
|
if (isset($_POST["data"])) {
|
|
|
|
// Functions to decrypt the payment request from Ecwid
|
|
|
|
function getEcwidPayload($app_secret_key, $data) {
|
|
// Get the encryption key (16 first bytes of the app's client_secret key)
|
|
$encryption_key = substr($app_secret_key, 0, 16);
|
|
|
|
// Decrypt payload
|
|
$json_data = aes_128_decrypt($encryption_key, $data);
|
|
|
|
// Decode json
|
|
$json_decoded = json_decode($json_data, true);
|
|
return $json_decoded;
|
|
}
|
|
|
|
function aes_128_decrypt($key, $data) {
|
|
// Ecwid sends data in url-safe base64. Convert the raw data to the original base64 first
|
|
$base64_original = str_replace(array('-', '_'), array('+', '/'), $data);
|
|
|
|
// Get binary data
|
|
$decoded = base64_decode($base64_original);
|
|
|
|
// Initialization vector is the first 16 bytes of the received data
|
|
$iv = substr($decoded, 0, 16);
|
|
|
|
// The payload itself is is the rest of the received data
|
|
$payload = substr($decoded, 16);
|
|
|
|
// Decrypt raw binary payload
|
|
$json = openssl_decrypt($payload, "aes-128-cbc", $key, OPENSSL_RAW_DATA, $iv);
|
|
//$json = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $payload, MCRYPT_MODE_CBC, $iv); // You can use this instead of openssl_decrupt, if mcrypt is enabled in your system
|
|
|
|
return $json;
|
|
}
|
|
|
|
// Get payload from the POST and decrypt it
|
|
$ecwid_payload = $_POST['data'];
|
|
|
|
// The resulting JSON from payment request will be in $order variable
|
|
$order = getEcwidPayload($client_secret, $ecwid_payload);
|
|
|
|
// Debug preview of the request decoded earlier
|
|
echo "<h3>REQUEST DETAILS</h3>";
|
|
|
|
// Account info from merchant app settings in app interface in Ecwid CP
|
|
$x_account_id = $order['merchantAppSettings']['merchantId'];
|
|
$api_key = $order['merchantAppSettings']['apiKey'];
|
|
|
|
// OPTIONAL: Split name field into two fields: first name and last name
|
|
$fullName = explode(" ", $order["cart"]["order"]["billingPerson"]["name"]);
|
|
$firstName = $fullName[0]; $lastName = $fullName[1];
|
|
|
|
// Encode access token and prepare calltack URL template
|
|
$ciphertext_raw = openssl_encrypt($order['token'],$cipher, $client_secret,$options=0,$iv,$tag);
|
|
$callbackPayload = base64_encode( $ciphertext_raw);
|
|
$callbackUrl = "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"."?storeId=".$order['storeId']."&orderNumber=".$order['cart']['order']['orderNumber']."&callbackPayload=".$callbackPayload;
|
|
|
|
// Request parameters to pass into payment gateway
|
|
$request = array(
|
|
"x_account_id" => $x_account_id,
|
|
"x_api_key" => $api_key,
|
|
"x_amount" => $order["cart"]["order"]["total"],
|
|
"x_currency" => $order["cart"]["currency"],
|
|
"x_customer_billing_address1" => str_replace(PHP_EOL, ' ' , $order["cart"]["order"]["billingPerson"]["street"]),
|
|
"x_customer_billing_city" => $order["cart"]["order"]["billingPerson"]["city"],
|
|
"x_customer_billing_country" => $order["cart"]["order"]["billingPerson"]["countryCode"],
|
|
"x_customer_billing_state" => $order["cart"]["order"]["billingPerson"]["stateOrProvinceCode"],
|
|
"x_customer_billing_postcode" => $order["cart"]["order"]["billingPerson"]["postalCode"],
|
|
"x_customer_email" => $order["cart"]["order"]["email"],
|
|
"x_customer_first_name" => $firstName,
|
|
"x_customer_last_name" => $lastName,
|
|
"x_customer_phone" => $order["cart"]["order"]["billingPerson"]["phone"],
|
|
"x_customer_shipping_address1" => str_replace(PHP_EOL, ' ', $order["cart"]["order"]["shippingPerson"]["street"]),
|
|
"x_customer_shipping_city" => $order["cart"]["order"]["shippingPerson"]["city"],
|
|
"x_customer_shipping_country" => $order["cart"]["order"]["shippingPerson"]["countryCode"],
|
|
"x_customer_shipping_state" => $order["cart"]["order"]["shippingPerson"]["stateOrProvinceCode"],
|
|
"x_customer_shipping_postcode" => $order["cart"]["order"]["shippingPerson"]["postalCode"],
|
|
"x_customer_shipping_phone" => $order["cart"]["order"]["shippingPerson"]["phone"],
|
|
"x_description" => "Order number". $order['cart']['order']['referenceTransactionId'],
|
|
"x_reference" => $order['cart']['order']['referenceTransactionId'],
|
|
"x_url_success" => $callbackUrl."&status=PAID",
|
|
"x_url_error" => $callbackUrl."&status=CANCELLED",
|
|
"x_url_cancel" => $order["returnUrl"]
|
|
);
|
|
|
|
// Sign the payment request
|
|
$signature = payment_sign($request,$api_key);
|
|
$request["x_signature"] = $signature;
|
|
|
|
// Print the request variables to debug
|
|
echo "<br/>";
|
|
foreach ($request as $name => $value) {
|
|
echo "$name: $value<br/>";
|
|
}
|
|
echo "<br/>";
|
|
|
|
// Print form on a page to submit it from a button press
|
|
echo "<form action='https://example.paymentpage.com/checkout' method='post' id='payment_form'>";
|
|
foreach ($request as $name => $value) {
|
|
echo "<input type='hidden' name='$name' value='$value'></input>";
|
|
}
|
|
echo "<input type='submit' value='Submit'>";
|
|
echo "</form>";
|
|
echo "<script>document.querySelector('#payment_form).submit();</script>";
|
|
|
|
}
|
|
|
|
// Function to sign the payment request form
|
|
function payment_sign($query, $api_key) {
|
|
$clear_text = '';
|
|
ksort($query);
|
|
foreach ($query as $key => $value) {
|
|
if (substr($key, 0, 2) === "x_") {
|
|
$clear_text .= $key . $value;
|
|
}
|
|
}
|
|
$hash = hash_hmac("sha256", $clear_text, $api_key);
|
|
return str_replace('-', '', $hash);
|
|
}
|
|
|
|
|
|
// If we are returning back to storefront. Callback from payment
|
|
|
|
if (isset($_GET["callbackPayload"]) && isset($_GET["status"])) {
|
|
|
|
// Set variables
|
|
$client_id = "test-rick-payment-template";
|
|
$c = base64_decode($_GET['callbackPayload']);
|
|
$token = openssl_decrypt($c, $cipher, $client_secret, $options=0, $iv,$tag);
|
|
$storeId = $_GET['storeId'];
|
|
$orderNumber = $_GET['orderNumber'];
|
|
$status = $_GET['status'];
|
|
$returnUrl = "https://app.ecwid.com/custompaymentapps/$storeId?orderId=$orderNumber&clientId=$client_id";
|
|
|
|
// Prepare request body for updating the order
|
|
$json = json_encode(array(
|
|
"paymentStatus" => $status,
|
|
"externalTransactionId" => "transaction_".$orderNumber
|
|
));
|
|
|
|
// URL used to update the order via Ecwid REST API
|
|
$url = "https://app.ecwid.com/api/v3/$storeId/orders/transaction_$orderNumber?token=$token";
|
|
|
|
// Send request to update order
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Content-Length: ' . strlen($json)));
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS,$json);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
// return customer back to storefront
|
|
echo "<script>window.location = '$returnUrl'</script>";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
header('HTTP/1.0 403 Forbidden');
|
|
echo 'Access forbidden!';
|
|
|
|
}
|
|
|
|
|
|
?>
|