Skip to content

Card payments and other gateways

Out of the box the shop takes bank transfer, cash on delivery, Bizum and crypto. That works without registering with anyone, and without anyone being able to close your account.

If you also want cards, five gateways are ready. Each one appears by itself as soon as you supply its credentials; without them, it does not exist.

Best for Brings
MONEI Spain Bizum, cards, PayPal, Apple and Google Pay
Stripe International Cards, Apple and Google Pay, SEPA, iDEAL
PayPal The button buyers ask for most PayPal and cards
Mollie Northern Europe iDEAL, Bancontact, cards, SEPA
Adyen High volume Cards and local methods across half the world

If you sell in Spain, MONEI. It is a Payment Institution licensed by the Bank of Spain, reaches the same banking rails as your bank’s card terminal, and does real Bizum.

What about Redsys? It is the terminal behind CaixaBank, BBVA and Santander, and it would be the first choice, but its official libraries are PHP, Java and .NET. Integrating it here would mean writing its cryptographic signature by hand, and in a payment that is the class of bug no test finds: the customer who got charged twice finds it. MONEI covers that ground without anybody inventing crypto.

Put the variables in the backend’s .env and restart. That is all.

Terminal window
# Spain: Bizum and cards
MONEI_API_KEY=pk_...
MONEI_WEBHOOK_URL=https://your-shop.com/hooks/payment/pasarela_monei
# International
STRIPE_API_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# PayPal
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=... # optional, see below
# The storefront's domain: buyers come back here after paying elsewhere
PAYMENT_RETURN_URL=https://your-shop.com

And in the storefront’s .env, if you use Stripe:

Terminal window
STRIPE_PUBLIC_KEY=pk_live_... # the public one, the browser sees it
SITE_URL=https://your-shop.com

The exact return path is set by the theme, because it changes with the page’s language: a buyer who was on /en/checkout comes back to /en/checkout/volver and does not land in Spanish right as they confirm the purchase.

Then you have to enable it at checkout: Panel → Settings → Regions, and tick it in the region it belongs to. Being configured is not being visible.

Stripe forbids it in its UK and EU terms, and so does Shopify Payments. It is not a technical problem and no amount of configuration fixes it: the account gets closed, usually without warning and with the money held.

Specialist processors charge 1 % to 5 % and hold reserves of up to 10 % for 180 days.

That is why the built-in methods — transfer, cash on delivery, Bizum and crypto — are not a stopgap until cards arrive. For that sector they are the reliable route, and for anyone they are what stops you depending on nobody objecting to what you sell.

What the buyer sees
Transfer, cash on delivery, Bizum Confirms the order and that is it
PayPal, Mollie, MONEI Goes to the gateway’s page and comes back
Stripe Pays without leaving the shop, in a Stripe form
Adyen Pays without leaving, in Adyen’s Drop-in with all its methods

With the last two the order is closed on return, not when the button is pressed. Closing it earlier would mean accepting orders nobody has paid for.

And the return page does not believe the URL. The buyer coming back only means “you may look now”: the gateway is asked, and if the money is not there, there is no order — however much it says ?success=true, which is something anyone can type.

With the others, on return you ask the gateway and you are done. With Adyen you cannot: its API cannot answer about a session without a value only the browser holds, and its own documentation says the result arrives asynchronously, in a webhook.

That webhook is HMAC-signed, so what it says can be trusted, and it carries the status and the amount. But it means one important thing:

With Adyen, the webhook has to arrive. If its URL is not reachable from the internet, the buyer pays and the order never closes. It does not work locally without a tunnel.

The Drop-in shows every method enabled on your account at once — cards, iDEAL, Klarna, whatever — and handles 3-D Secure itself.

With soloAutorizar the gateway holds the money when the order is placed and does not take it until you capture it from the panel, on dispatch.

It is for not charging for what you might not be able to ship. But a hold expires — from a few days to a month depending on the gateway — and once it does there is nothing to charge. If you are not going to prepare orders quickly, do not turn it on.

In the panel, a held payment shows as authorised, not paid. That is deliberate: shipping against a hold is giving the goods away if it expires.

Every gateway notifies the shop when a payment changes. The URL is always:

https://your-shop.com/hooks/payment/pasarela_<gateway>

What arrives in that notification is not believed. The signature is checked, only the payment’s identifier is taken from it, and the gateway is asked again what the status is. That sounds excessive until you think about what a webhook is: a request anyone who guesses the URL can send, saying “this one is paid”.

Per-gateway details:

  • Stripe and Adyen sign with a secret. With no secret configured, the shop accepts none of their notifications — an open route that marks orders as paid cannot be left alive by an oversight in .env.
  • MONEI signs with its own header; its SDK validates it.
  • Mollie does not sign, on purpose: its notification carries only the identifier precisely so that you have to ask again.
  • PayPal uses no shared secret: you ask PayPal whether the notification is theirs. PAYPAL_WEBHOOK_ID performs that check; without it, the safety comes from re-reading the order.

“falta el paquete stripe — the gateway is configured but its library is not installed. npm i stripe in the backend.

“La pasarela «adyen» necesita merchantAccount” — this appears at start-up, not at checkout, and that is deliberate: a misconfigured gateway has to show its face before there is a customer at the checkout.

A payment stays “pending” — look at the gateway’s notification log. Almost always it is the webhook URL, wrong or unreachable from outside.

The five live in @pcreative/payments-contract, each on top of its gateway’s official SDK. To add one more you implement six operations — create, read, capture, cancel, refund and webhook — and register it like the rest. The rest of the system never notices.