Reuse existing PaymentIntent instead of blocking on retry
When user abandons checkout and retries, retrieve the existing PaymentIntent from Stripe. If still usable (requires_payment_method, requires_confirmation, requires_action), return its client_secret. If already succeeded, block with clear error. If terminal/canceled, clear and create new one. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
29327c4a13
commit
d0f0f86176
1 changed files with 37 additions and 8 deletions
|
|
@ -83,14 +83,43 @@ try {
|
||||||
WHERE o.ID = :orderID
|
WHERE o.ID = :orderID
|
||||||
", { orderID: orderID }, { datasource: "payfrit" });
|
", { orderID: orderID }, { datasource: "payfrit" });
|
||||||
|
|
||||||
// Check if order already has a PaymentIntent (idempotency check)
|
// Check if order already has a PaymentIntent - retrieve and reuse if still valid
|
||||||
if (qOrder.recordCount > 0 && len(trim(qOrder.StripePaymentIntentID ?: "")) > 0) {
|
existingPiId = qOrder.StripePaymentIntentID ?: "";
|
||||||
response["OK"] = false;
|
if (qOrder.recordCount > 0 && len(trim(existingPiId)) > 0) {
|
||||||
response["ERROR"] = "existing_payment_intent";
|
// Retrieve existing PaymentIntent from Stripe
|
||||||
response["MESSAGE"] = "Order already has a PaymentIntent. Use existing checkout session or retry payment.";
|
piRetrieve = new http();
|
||||||
response["PAYMENT_INTENT_ID"] = qOrder.StripePaymentIntentID;
|
piRetrieve.setMethod("GET");
|
||||||
writeOutput(serializeJSON(response));
|
piRetrieve.setUrl("https://api.stripe.com/v1/payment_intents/#existingPiId#");
|
||||||
abort;
|
piRetrieve.setUsername(stripeSecretKey);
|
||||||
|
piRetrieve.setPassword("");
|
||||||
|
piResult = piRetrieve.send().getPrefix();
|
||||||
|
existingPi = deserializeJSON(piResult.fileContent);
|
||||||
|
|
||||||
|
if (!structKeyExists(existingPi, "error")) {
|
||||||
|
piStatus = existingPi.status ?: "";
|
||||||
|
// Reusable states: can still complete payment
|
||||||
|
if (listFindNoCase("requires_payment_method,requires_confirmation,requires_action", piStatus)) {
|
||||||
|
// Return existing PaymentIntent - user can retry with same one
|
||||||
|
response["OK"] = true;
|
||||||
|
response["CLIENT_SECRET"] = existingPi.client_secret;
|
||||||
|
response["PAYMENT_INTENT_ID"] = existingPi.id;
|
||||||
|
response["PUBLISHABLE_KEY"] = application.stripePublishableKey ?: "pk_test_sPBNzSyJ9HcEPJGC7dSo8NqN";
|
||||||
|
response["REUSED"] = true;
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
abort;
|
||||||
|
} else if (piStatus == "succeeded") {
|
||||||
|
// Already paid - don't create another
|
||||||
|
response["OK"] = false;
|
||||||
|
response["ERROR"] = "already_paid";
|
||||||
|
response["MESSAGE"] = "This order has already been paid.";
|
||||||
|
writeOutput(serializeJSON(response));
|
||||||
|
abort;
|
||||||
|
}
|
||||||
|
// Other terminal states (canceled, etc.) - clear and create new
|
||||||
|
}
|
||||||
|
// PaymentIntent not found or terminal - clear it from order
|
||||||
|
queryExecute("UPDATE Orders SET StripePaymentIntentID = NULL WHERE ID = :orderID",
|
||||||
|
{ orderID: orderID }, { datasource: "payfrit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
deliveryFee = 0;
|
deliveryFee = 0;
|
||||||
|
|
|
||||||
Reference in a new issue