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
|
||||
", { orderID: orderID }, { datasource: "payfrit" });
|
||||
|
||||
// Check if order already has a PaymentIntent (idempotency check)
|
||||
if (qOrder.recordCount > 0 && len(trim(qOrder.StripePaymentIntentID ?: "")) > 0) {
|
||||
response["OK"] = false;
|
||||
response["ERROR"] = "existing_payment_intent";
|
||||
response["MESSAGE"] = "Order already has a PaymentIntent. Use existing checkout session or retry payment.";
|
||||
response["PAYMENT_INTENT_ID"] = qOrder.StripePaymentIntentID;
|
||||
writeOutput(serializeJSON(response));
|
||||
abort;
|
||||
// Check if order already has a PaymentIntent - retrieve and reuse if still valid
|
||||
existingPiId = qOrder.StripePaymentIntentID ?: "";
|
||||
if (qOrder.recordCount > 0 && len(trim(existingPiId)) > 0) {
|
||||
// Retrieve existing PaymentIntent from Stripe
|
||||
piRetrieve = new http();
|
||||
piRetrieve.setMethod("GET");
|
||||
piRetrieve.setUrl("https://api.stripe.com/v1/payment_intents/#existingPiId#");
|
||||
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;
|
||||
|
|
|
|||
Reference in a new issue