DEV Community

EcomTrade24 Pay
EcomTrade24 Pay

Posted on

A Payment Button Is Not a Payment System

A Payment Button Is Not a Payment System

A lot of ecommerce integrations start with one simple goal:

“Add a payment button.”

That sounds reasonable.

The merchant wants to get paid. The developer connects a provider. A checkout URL is created. The customer pays. A webhook updates the order.

Done.

At least, that is how it looks in the happy path.

But in real ecommerce, the happy path is only one part of the system.

Payments fail.

Webhooks arrive late.

Customers abandon 3D Secure.

Providers decline transactions.

Order statuses get stuck.

A payment session expires.

A customer tries again with another method.

The provider says “paid” but the shop never updates.

The shop says “pending” but the customer was already charged.

That is why a payment button is not a payment system.

A payment system needs to handle what happens when things are not perfect.

The happy path is easy

The easiest version of a checkout flow looks like this:

Customer -> Checkout -> Payment Provider -> Success -> Order Paid
Enter fullscreen mode Exit fullscreen mode

For a test transaction, this is usually enough.

But production traffic is different.

A more realistic flow looks like this:

Customer -> Checkout Session Created
         -> Redirected to Hosted Checkout
         -> Chooses Payment Method
         -> Provider Processing
         -> Webhook Sent
         -> Order Status Updated
         -> Customer Redirected
Enter fullscreen mode Exit fullscreen mode

Even this is still too simple.

Because each step can fail or arrive in a different order.

The customer redirect may happen before the webhook.

The webhook may arrive before the customer returns.

The customer may close the browser.

The provider may mark the payment as under review.

The session may expire.

The same webhook may be sent twice.

If the integration is not built for that, the merchant ends up with support tickets.

Webhooks should be treated as the source of truth

A common mistake is relying too much on the redirect URL.

The customer reaches a success page, so the shop marks the order paid.

That is risky.

A redirect tells you that the customer’s browser moved somewhere.

A webhook tells your backend what the payment provider says happened.

The backend should usually treat webhook confirmation as the important event.

A better pattern is:

Customer redirect = user experience
Webhook event     = payment confirmation
Enter fullscreen mode Exit fullscreen mode

The redirect can show a friendly message like:

Thanks, we are confirming your payment.

The webhook should update the order when the final status is known.

Payment statuses need clear rules

Do not build payment handling around only success and failed.

That is not enough.

A practical checkout system usually needs statuses like:

created
pending
paid
failed
expired
cancelled
review
refunded
Enter fullscreen mode Exit fullscreen mode

Not every provider uses the same words, but your internal system should normalize them.

For example:

provider_status: "completed"
internal_status: "paid"
Enter fullscreen mode Exit fullscreen mode

Or:

provider_status: "under_review"
internal_status: "review"
Enter fullscreen mode Exit fullscreen mode

This gives the merchant dashboard and shop plugin a consistent way to understand what happened.

Idempotency matters

Webhooks can be sent more than once.

That is normal.

Your system should be safe if it receives the same webhook multiple times.

Bad pattern:

Webhook received -> Add wallet credit
Webhook received again -> Add wallet credit again
Enter fullscreen mode Exit fullscreen mode

Better pattern:

Webhook received
Check if event/payment already processed

If not processed:
    update order
    create transaction record
    mark event as processed

If already processed:
    ignore safely
Enter fullscreen mode Exit fullscreen mode

A payment confirmation should be repeat-safe.

That one rule prevents a lot of ugly accounting problems.

Store provider references

When creating a payment session, store your own internal ID and the provider’s reference.

Example:

merchant_order_id
payment_session_id
provider_payment_id
customer_email
amount
currency
status
created_at
expires_at
Enter fullscreen mode Exit fullscreen mode

This makes debugging possible later.

When a merchant asks, “What happened to this order?”, you do not want to search through random logs.

You want a clean payment session record.

Checkout expiration should be handled

A hosted checkout session should not live forever.

If a customer opens checkout and never pays, the system needs to know when that session is no longer valid.

That helps with:

  • Cleaning up pending orders
  • Showing accurate merchant dashboard data
  • Preventing old checkout links from being reused
  • Reducing customer confusion
  • Avoiding manual support work

An expired session is not the same as a failed payment.

A failed payment means payment was attempted and did not succeed.

An expired session may mean the customer never completed checkout.

Those are different events and should be tracked differently.

Fallback is part of payment infrastructure

A single provider is easy to integrate.

But a single provider can also become a single point of failure.

If the card route fails, what happens next?

Does the customer get another option?

Can the merchant send a payment link?

Can the checkout offer a different method?

Can local methods be shown where available?

Can crypto settlement be used where it makes sense?

Can the system route based on amount, country or method?

This is where Smart Routing becomes useful.

A simplified routing idea could look like this:

if method == "card" and amount >= card_minimum:
    route_to_best_card_provider()
elif method == "bank_transfer" and country_supported:
    route_to_local_bank_method()
elif method == "crypto":
    route_to_crypto_checkout()
else:
    show_available_fallback_methods()
Enter fullscreen mode Exit fullscreen mode

The customer should not see all the complexity.

The merchant should benefit from it.

WooCommerce needs extra care

WooCommerce is flexible, but order status handling needs discipline.

A clean payment plugin should think about:

  • Creating a payment session from the backend
  • Never exposing secret API keys in frontend JavaScript
  • Redirecting the customer to hosted checkout
  • Listening for webhook confirmation
  • Updating the WooCommerce order status correctly
  • Adding order notes for traceability
  • Handling failed, expired and review events
  • Avoiding duplicate payment confirmation
  • Showing useful diagnostics to the merchant

The plugin should not just say “payment failed.”

It should help the merchant understand why the payment failed, where possible.

A useful payment log is not optional

Logs are not glamorous.

But when real money is involved, they matter.

At minimum, a merchant or admin should be able to see:

Session created
Customer redirected
Provider selected
Webhook received
Status changed
Order updated
Error message if something failed
Enter fullscreen mode Exit fullscreen mode

Without this, every issue becomes guesswork.

And guesswork is expensive.

Keep the customer experience simple

More routing behind the scenes should not mean a messy checkout.

The customer should see a clean payment flow.

The system can handle provider selection, fallback rules, limits and settlement logic in the background.

That is the balance:

Simple for the customer
Flexible for the merchant
Traceable for the developer
Enter fullscreen mode Exit fullscreen mode

A good payment system needs all three.

The real checklist

Before calling a payment integration “done”, ask:

  • What happens if the webhook is delayed?
  • What happens if the webhook is sent twice?
  • What happens if the customer closes the browser?
  • What happens if the payment is under review?
  • What happens if the session expires?
  • What happens if the provider declines the transaction?
  • What happens if the customer wants another method?
  • What happens if WooCommerce shows pending but the provider says paid?
  • Can the merchant see a useful log?
  • Can support understand what happened without asking a developer?

If the answer is “we do not know”, the integration is not finished.

It only handles the happy path.

Final thought

Adding a payment button is easy.

Building a payment system is harder.

The difference is not the first successful test transaction.

The difference is what happens when production traffic gets messy.

That is where good checkout infrastructure matters: hosted checkout, clear webhooks, safe status handling, fallback routes, useful logs and routing logic that helps merchants keep sales moving.

EcomTrade24 Pay is built around that idea for ecommerce merchants, WooCommerce shops and businesses that need more than one fragile payment path.

Learn more about EcomTrade24 Pay for Hosted Checkout, WooCommerce, API/webhook automation and Smart Routing:

https://pay.ecomtrade24.com

Top comments (0)