I am using Stripe quick start code found here https://stripe.com/docs/payments/quickstart I have narrowed down the problem to the "items" array in the checkout.js not being recognized or not properly constructed or whatever in the create.php file. My customers will only be purchasing one type of item at different dollar amounts so this function
function calculateOrderAmount(array $items): int {
// Replace this constant with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
}
is useless to me and the amount can just go into this
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => calculateOrderAmount($jsonObj->items)
,
'currency' => 'eur',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
as the total.
I have tried replacing calculateOrderAmount($jsonObj->items)
with array_values($$items)[0] as well as everything else I can think of. Stripe support has been of no help.
Thank you in advance
The HTML: The price is in cents from the "value"
<select id="item-options"">
<option value="">---- SELECT----</option>
<option value="9000">300 ITEMS - $90.00 USD</option>
<option value="8000">200 ITEMS - $80.00 USD</option>
<option value="5000">100 ITEMS - $50.00 USD</option>
<option value="3000">50 ITEMS - $30.00 USD</option>
<option value="1750">25 ITEMS - $17.50 USD</option>
<option value="800">10 ITEMS - $8.00 USD</option>
<option value="450">5 ITEMS - $4.50 USD</option>
<option value="100">1 ITEMS - $1.00 USD</option>
</select>
The full javascript:
var price = $('#item-options option:selected').val();
// This is your test publishable API key.
const stripe = Stripe("pk_test_xxxxxxxxxxx....");
// The items the customer wants to buy I have change this to (amount: price) const items = [{ id: "xl-tshirt" }];
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/create.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:4242/public/checkout.html",
receipt_email: document.getElementById("email").value,
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
The php :
<?php
require 'vendor/autoload.php';
// This is your test secret API key.
\Stripe\Stripe::setApiKey('sk_test_XXXXXX.........');
function calculateOrderAmount(array $items): int {
// Replace this constant with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
}
header('Content-Type: application/json');
try {
// retrieve JSON from POST body
$jsonStr = file_get_contents('php://input');
$jsonObj = json_decode($jsonStr);
// Create a PaymentIntent with amount and currency
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => calculateOrderAmount($jsonObj->items),
'currency' => 'eur',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
CodePudding user response:
To prevent price manipulation on the client side you should pass to your PHP script ids of products, not the prices directly. By knowing the product ids, you can search for prices on the server side. That's what the calculateOrderAmount
function is for in your example code.
Since you intended to sell only one type of product, the price of which will depend on the volume, you may want to send a number of items from the client. Then take a look at tiered pricing and make the code match your pricing model.
CodePudding user response:
I solved this by doing a var_dump($items) in the php file and the console.log showed it as a stdClass Object. Using "$amount = $items[0]->amount;" in the "function calculateOrderAmount(array $items)" now gives me the correct price for the "$paymentIntent = \Stripe\PaymentIntent::create([ 'amount' => calculateOrderAmount($jsonObj->items),"
Thank you Justin for responding to my question.