Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Stop refunding payments you should never have charged

I once shipped a checkout that charged the card the instant the customer hit pay, then ran the order validation afterward. Stock check, address validation, a fraud heuristic, a third-party availability call. When any of those failed, the…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

I once shipped a checkout that charged the card the instant the customer hit pay, then ran the order validation afterward. Stock check, address validation, a fraud heuristic, a third-party availability call. When any of those failed, the code did the obvious thing: it refunded the payment



It worked. It also generated a steady trickle of confused, angry emails. "Why did you charge me 89 euros and then refund it three days later?" To the customer, a charge followed by a refund does not read as "we caught a problem". It reads as "this company is sketchy and now my money is stuck in limbo for a week"



The fix was one property I had been ignoring for years: capture_method: manual






The charge-then-refund antipattern



Here is the flow almost every tutorial teaches, and the one I had shipped:




const intent = await stripe.paymentIntents.create({
amount: 8900,
currency: 'eur',
payment_method: paymentMethodId,
confirm: true,
});

// money has now left the customer's account

const order = await validateOrder(cart);
if (!order.ok) {
await stripe.refunds.create({ payment_intent: intent.id });
throw new Error('Order validation failed after charge');
}






The problem is the comment. By the time you run validateOrder, the money is gone. The charge has hit the customer's statement, your statement, and in cross-border cases an FX conversion. If validation fails, you are not undoing a mistake, you are issuing a second financial event that has to settle on its own timeline



That has real costs:




  • The customer sees a charge and a refund, days apart, and loses trust

  • Refunds can take 5 to 10 business days to land back on the customer's statement

  • You may not get every fee back, and FX swings mean the refund amount can differ from the charge

  • A pattern of charges-then-refunds is exactly what card networks flag as a risk signal



You did everything to be honest and it still looks bad. The flow is the problem, not your intentions






Authorize, then capture



Cards have always supported a two-step model: authorization places a hold on the funds without moving them, and capture actually moves the money. Hotels and car rental companies have used this forever. The hold sits on the customer's card, the money never leaves until you decide it should, and if you never capture, the hold simply expires



Stripe exposes this with one property on the PaymentIntent:




const intent = await stripe.paymentIntents.create({
amount: 8900,
currency: 'eur',
payment_method: paymentMethodId,
capture_method: 'manual',
confirm: true,
});

// funds are AUTHORIZED, not captured, nothing has moved yet

const order = await validateOrder(cart);

if (order.ok) {
await stripe.paymentIntents.capture(intent.id);
} else {
await stripe.paymentIntents.cancel(intent.id);
}






cancel releases the hold. No charge ever appeared, so there is no refund to explain. The customer sees a pending authorization that quietly drops off. You moved the risky business logic to where it belongs: between the authorization and the capture, while the money is still reversible without a trace






What you get for free



Partial capture. You can capture less than you authorized. Authorize 89 euros for a cart, discover one item is out of stock, capture 64 euros and the rest of the hold releases automatically:




await stripe.paymentIntents.capture(intent.id, {
amount_to_capture: 6400,
});






A real validation window. Stripe holds an uncaptured authorization for about 7 days. That is plenty of time for a synchronous stock check, and enough for some asynchronous flows too. If you never capture, you never charged



A clean audit trail. "Authorized, then cancelled" is a single coherent story in your logs and in the customer's mind. "Charged, then refunded" is two events that have to be reconciled, and that reconciliation is where money quietly goes missing






This is not Stripe-specific



The property name changes, but every serious payment provider exposes the same authorize-then-capture model:





  • Stripe: capture_method: 'manual' on the PaymentIntent, then paymentIntents.capture() or paymentIntents.cancel()


  • Adyen: set a manual capture delay, then call /payments to authorize and /payments/{id}/captures to capture


  • Braintree: pass submitForSettlement: false to transaction.sale, then transaction.submitForSettlement(id) later


  • PayPal: create the order with intent: 'AUTHORIZE', then capture the authorization



The vocabulary differs, the shape is identical: get a hold, do your work, settle or release






When you should not use it





  • Instant digital goods. If you deliver the moment payment succeeds and there is nothing to validate, the extra round trip buys you nothing


  • Subscriptions and recurring billing. These run on their own automated capture flow


  • Validation slower than the hold. If your checks can take longer than 7 days, the authorization will expire before you capture


  • Methods that do not support it. Some non-card payment methods do not offer separate auth and capture — confirm support before you build around it






The rule



If there is any business logic that can fail after the customer has paid but before you are willing to keep their money, that logic belongs between an authorization and a capture



The charge-then-refund flow is the default in almost every example online, which is exactly why it ends up in production. One property moves the risky work to the right side of the money, and the angry emails stop






Originally published on jguillaumesio.com

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Stop refunding payments you should never have charged

Thematisch verwandte Begriffe: Stop, refunding, payments, should · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-101042 | Parse Server is an open-source backend server. In versions >= 9.0.0 < 9…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag