SumUp Online Payments for OpenCart 4.1, integrating with the SumUp API: take online payments with the SumUp account you already use for your card reader. Cards, Apple Pay, Google Pay and the local methods SumUp enables for you, in the embedded SumUp Payment Widget on your checkout page or on the SumUp Hosted Checkout page. An order counts as paid only after the shop read the checkout back from SumUp with your own API key and the transaction, amount and currency equal the order; declined cards get a message and a retry, processing payments wait as Pending orders and are never shown as cancelled. Full and partial refunds from the order page (the transaction is read first, an unclear refund is never sent twice), webhooks on a parameter-free endpoint, cron read-back, live and sandbox merchant side by side, SameSite-safe return, encrypted API keys. Events and cron, no OCMOD.
On saleVersion 1.0.0Compatible with OpenCart 4.1.0.4 No core file edits
Built forOpenCart
Payment Widget or Hosted Checkout1 of 5
SumUp Online Payments for OpenCart 4.1, integrating with the SumUp API: take online payments with the SumUp account you already use for your card reader. Cards, Apple Pay, Google Pay and the local methods SumUp enables for you, in the embedded SumUp Payment Widget on your checkout page or on the SumUp Hosted Checkout page. An order counts as paid only after the shop read the checkout back from SumUp with your own API key and the transaction, amount and currency equal the order; declined cards get a message and a retry, processing payments wait as Pending orders and are never shown as cancelled. Full and partial refunds from the order page (the transaction is read first, an unclear refund is never sent twice), webhooks on a parameter-free endpoint, cron read-back, live and sandbox merchant side by side, SameSite-safe return, encrypted API keys. Events and cron, no OCMOD.
OpenCart 4.1.0.4
Overview
Take online payments in OpenCart 4.1 with the SumUp account you already have: cards, Apple Pay, Google Pay and the local methods SumUp enables for your business (for example iDEAL, Bancontact, BLIK, EPS, Przelewy24, PayPal, PIX or Boleto, depending on your country). The money lands in the same SumUp account, payouts and dashboard as your card-reader sales. The customer pays either in the embedded SumUp Payment Widget on your checkout page or on the SumUp Hosted Checkout page; card details go straight to SumUp and never touch your server.
What it does
Two ways to pay. The Payment Widget (SumUp's own form, mounted on the payment step; 3D Secure is shown in place) or Hosted Checkout (the SumUp payment page with your branding from the SumUp dashboard). Switch with one setting.
Paid means paid. The widget's answer and SumUp's webhook are only triggers: an order moves to your paid status only after the shop read the checkout back from SumUp with your own API key, the checkout reference is ours, the transaction is SUCCESSFUL and its amount and currency equal what the shop sent AND what the order costs now. Money that does not match (another amount, a second payment of a paid order, a sandbox payment, a payment for an order you closed meanwhile) is kept, flagged on the order and never marks it paid; you can refund it, and that refund never changes the order status or the stock.
Declined is not cancelled. A declined card shows a clear message on the payment step and a "Try again" button (a new checkout for the same order). A payment SumUp still processes moves the order to Pending and the customer to a page that updates itself; it is never shown as cancelled.
Full and partial refunds from a SumUp tab on the order page. Before every refund the shop reads the transaction from SumUp (refunds made in the SumUp dashboard and chargebacks count). SumUp's refund API has no idempotency key, so a refund without a clear answer is never sent twice: it is matched with the transaction's refund events.
Webhooks plus recovery. Every checkout carries a parameter-free webhook address (/extension/kv_sumup/notify.php); SumUp's CHECKOUT_STATUS_CHANGED triggers a read-back. An hourly cron reads open checkouts back when a webhook or the customer's browser got lost.
Live and sandbox side by side. Store your live merchant and a sandbox merchant (Developer Settings, Sandboxes) and switch with one click. Sandbox payments never count as real money unless you allow it; the payment step says "Test mode". A sandbox checkout of exactly 11.00 always fails, which is how you test a declined payment.
Safe return. OpenCart 4.1 sets its session cookie with SameSite=Strict, so the customer comes back from 3D Secure, a redirect method or the hosted page through a small same-site return file; a customer who paid and never returned finds the order released at the next checkout or basket visit (never paid twice).
Your keys, kept safe. API keys are stored encrypted and never shown again; the log masks keys, card digits and buyer data. Updates and uninstall keep your payment records, settings and keys.
Requirements
Tested on OpenCart 4.1.0.4 (built for 4.1.x), PHP 8.1 or newer with cURL and OpenSSL.
A SumUp merchant account with a secret API key (SumUp dashboard: Settings, For Developers, Toolkit, API Keys).
The currency of your SumUp account enabled in the store and used by the customer: SumUp is offered only when the order currency equals the checkout currency (one of BRL, CHF, CLP, COP, CZK, DKK, EUR, GBP, HUF, NOK, PLN, RON, SEK, USD), so the amount sent always equals the order.
HTTPS for the live shop (the Payment Widget requires it in production; the webhook needs a public https address).
SumUp is a trademark of its owner. This extension is independent software by Kavio Labs that integrates with the SumUp API; it is not affiliated with or endorsed by SumUp.
Installation
In the OpenCart admin open Extensions > Installer and upload kv_sumup.ocmod.zip.
Click the green Install button next to "SumUp Online Payments" in the installer list.
Open Extensions > Extensions, choose the type Payments, find SumUp Online Payments and click Install (the plus button), then Edit (the pencil).
Enter your licence key on the License tab (the extension works for 14 days without a key; the admin is never blocked).
Upload the new kv_sumup.ocmod.zip in Extensions > Installer the same way. OpenCart 4 updates an extension by uninstalling and installing it; SumUp Online Payments keeps its payment records, refunds, log, settings, API keys and licence state across that, so nothing has to be entered again.
Uninstalling
Extensions > Extensions > Payments > SumUp Online Payments > Uninstall removes the events and the cron job. Your payment records and settings stay (so a later re-install or update finds them). To delete everything on purpose use Remove all data on the Log and cron tab (type DELETE to confirm; refused while a payment is still processing).
No core files are changed and no OCMOD modification is used.
Configuration
Open Extensions > Extensions > Payments > SumUp Online Payments > Edit. The header shows at a glance whether the method is enabled, the mode (Live or Sandbox), the connected merchant, the checkout type and the currency.
Connection tab
Mode: Live or Sandbox.
Live merchant: your merchant code (for example MH4H92C7, shown in the SumUp dashboard) and a secret API key (Settings, For Developers, Toolkit, API Keys, Create). Keys are stored encrypted; leave the field empty to keep the stored key.
Sandbox merchant (optional): the merchant code and secret API key of a sandbox merchant from Developer Settings, Sandboxes. Sandbox merchants move no real money.
Click Save and test connection. The shop reads your merchant profile and the payment methods SumUp enabled for you and shows them (for example card, apple_pay, google_pay). An empty Checkout currency is filled with your account currency.
Accept sandbox payments: leave unticked on a live shop. When ticked, sandbox payments are booked as paid with a SANDBOX PAYMENT note and no e-mail.
Webhook URL: shown for reference. The shop sends it with every checkout (return_url), so there is nothing to register in the SumUp dashboard. It needs a public https address.
Payment tab
Status: offers SumUp at checkout.
Checkout type: Payment Widget (embedded) or Hosted Checkout.
Name at checkout and Note under the button.
ZIP code field: show it in the widget (SumUp requires it for merchants in the USA).
Google Pay merchant ID / name: only for Google Pay inside the Payment Widget; the ID comes from the Google Pay & Wallet Console (it is not your SumUp merchant code).
Geo zone and Sort order.
Order statuses tab
Setting
Default
When it is used
Payment processing
Pending
SumUp reports a payment as still processing (PENDING). Never the cancelled status.
Paid
Processing
The checkout is read back as paid and equals the order.
Cancelled or expired
Canceled
SumUp expired the checkout or cancelled the last processing payment of a waiting order. Never on a paid order.
Fully refunded
Refunded
The whole order was refunded (None = keep the status).
E-mail the customer
on
The order e-mail when the payment is confirmed.
Log and cron tab
Debug log switch, the log table, the cron command and a secret cron URL. OpenCart's own cron runs the SumUp job every hour; make sure the OpenCart cron runs (*/10 * * * * php <store>/cron.php).
Daily use
What the customer sees
Payment Widget: on the payment step the customer clicks Pay with card or wallet; the SumUp form opens in place with the methods SumUp offers for your account. After paying the customer lands on the order confirmation. A declined card shows a message and Try again (a fresh checkout for the same order); leaving the form and coming back opens the same checkout again.
Hosted Checkout: Continue to SumUp opens the SumUp payment page; after paying, the button on SumUp's success page brings the customer back to the confirmation. The same hosted page is reused for 25 minutes (SumUp keeps it for 30).
A payment SumUp still processes shows a waiting page that updates itself and moves on to the confirmation as soon as SumUp confirms.
The SumUp tab on the order page
Every order paid with SumUp has a SumUp tab: the checkout, its reference, the merchant code, SumUp's status, each transaction (id, transaction code, status, amount, refunded) and every refund.
Refund: enter an amount (empty = everything that is left) and an optional reason. A whole untouched payment is refunded as a full refund, anything else as a partial refund. Only a full refund of the payment booked for the order sets the Refunded status; refunding money that was never booked (a duplicate, a mismatch, a sandbox payment) adds a note only.
Read back: reads the checkout and the transaction from SumUp again, including refunds made in the SumUp dashboard and chargebacks.
Flags you may see
Money held, not booked for this order: SumUp holds money that does not fit the order (another amount, the order changed meanwhile, a sandbox payment, or the order was already closed). The order is not marked paid; refund it if it is not wanted.
Second payment: not booked: the customer paid the same order twice (for example in two tabs). Refund the second payment.
Refund awaiting answer: SumUp did not answer a refund clearly. It is never sent twice; the shop matches it with the transaction's refund events (or marks it failed after 24 hours with a note). Check the transaction in the SumUp dashboard before refunding again.
Testing with a sandbox merchant
Switch the mode to Sandbox, use SumUp's test cards (for example 4200 0000 0000 0091, any future expiry, any CVV) and an order total of exactly 11.00 to see a declined payment.
Developer notes
Technical reference for developers who customize SumUp Online Payments. Every endpoint and field below comes from the official SumUp developer documentation (developer.sumup.com and its OpenAPI document openapi.json); see Conformance for what was checked and the points to know.
Identity
Item
Value
Extension code
kv_sumup (OpenCart 4.1, type payment)
Route
extension/kv_sumup/payment/kv_sumup
Namespace
KavioLabs\KvSumup (shared core in system/library/)
Settings
payment_kv_sumup_* (api_key and sandbox_api_key sealed v2:)
<shop>/extension/kv_sumup/notify.php (fixed route, no query parameters; sent as the checkout's return_url)
Return
<shop>/extension/kv_sumup/return.php (boots nothing, no cookie; continues same-site to .back; sent as redirect_url and opened after the widget's success event)
OpenCart cron job kv_sumup (hourly) or index.php?route=extension/kv_sumup/payment/kv_sumup.cron&key=<cron key>
Endpoints used
Host https://api.sumup.com, header Authorization: Bearer <secret API key> on every call (live and sandbox merchants use the same host with their own key).
Call
Endpoint
Used for
Create a checkout
POST /v0.1/checkouts
Start a payment attempt (checkout_reference stored first)
Retrieve a checkout
GET /v0.1/checkouts/{id}
The only source of a payment's state
List checkouts
GET /v0.1/checkouts?checkout_reference=
Adopt a checkout after a create call without a clear answer or a 409 DUPLICATED_CHECKOUT
Refund a transaction
POST /v1.0/merchants/{merchant_code}/payments/{transaction_id}/refunds
Full (no body) and partial ({"amount"}) refunds
Retrieve a transaction
GET /v2.1/merchants/{merchant_code}/transactions?id=
Refund and chargeback events (dashboard refunds, refund reconciliation)
Retrieve a merchant
GET /v1/merchants/{merchant_code}
Test connection, sandbox flag, account currency (cached 12 h)
Payment methods
GET /v0.1/merchants/{merchant_code}/payment-methods
Shown by Test connection
States
kv_sumup_txn.status: created (checkout ready, nothing paid), pending (a transaction is PENDING), paid, partial_refund, refunded, disputed (chargeback), failed (declined or create failed), canceled (transaction CANCELLED), expired (checkout EXPIRED or unknown after 2 days). Transaction status mapping: SUCCESSFUL -> paid, REFUNDED -> paid with the refunded amount from the REFUND events, FAILED -> failed, CANCELLED -> canceled, PENDING and anything new -> pending. A settled state never moves back. error_code = MISMATCH marks money held that is not booked for the order (amount, currency, sandbox payment, a second paid attempt, a closed order); duplicate = 1 marks the second paid attempt.
Concurrency and idempotency
One GET_LOCK per order (scoped to the shop's database and table prefix) around every start, read-back, refund and webhook; the webhook waits 5 seconds and answers 503 when busy, so SumUp retries.
The attempt row with its checkout_reference (kvsu-<order>-<attempt>-<8 hex>) is written before the create call; a lost answer is looked up by reference, never created twice.
Refunds: the row is written before the call; the transaction is read first (GET .../transactions?id=); an unclear answer (no answer, 5xx, 429) is never resent and is matched with an unclaimed REFUND event of the same amount; after 24 hours without a match it is marked failed with an order note.
Hidden settings for tests
payment_kv_sumup_base_url points the API and the widget script to a local mock server; it is honoured for loopback hosts only (127.0.0.1, localhost, [::1]), so a test can never reach the real API.
Conformance with the SumUp documentation
Checked 2026-11-09 against developer.sumup.com: the OpenAPI document (https://developer.sumup.com/openapi.json), the Online Payments, Payment Widget, Hosted Checkout, Webhooks, Testing, API keys and Refund pages, and the official sumup/sumup-php SDK. Nothing here was run against a real SumUp account: the tests use a stand-in built from the OpenAPI document.
Verified in the documentation
Create a checkout POST /v0.1/checkouts: required checkout_reference (up to 64 characters), amount (number, major units), currency (enum), merchant_code; optional description, return_url (webhook), redirect_url (3DS / redirect methods / Hosted Checkout success button), hosted_checkout.enabled; 201; 409 DUPLICATED_CHECKOUT; 400 {message, error_code, param}; 403 {error_message, error_code, status_code}; 401 problem details.
Retrieve a checkout GET /v0.1/checkouts/{id}: status PENDING | FAILED | PAID | EXPIRED, transactions[] (id, transaction_code, amount, currency, statusSUCCESSFUL | CANCELLED | FAILED | PENDING | REFUNDED); 404 NOT_FOUND. List checkouts GET /v0.1/checkouts?checkout_reference=.
Refund a transaction POST /v1.0/merchants/{merchant_code}/payments/{transaction_id}/refunds: no body = full refund, {"amount"} = partial in major units; 201 {}; errors 400, 403, 404, 409 (not refundable in its current state), 422 (errors[].codeINVALID_AMOUNT, reasonamount_too_high). The official PHP SDK uses the same path.
Retrieve a transaction GET /v2.1/merchants/{merchant_code}/transactions?id=: events[] of type REFUND / CHARGE_BACK with status and amount, simple_status (CHARGEBACK).
Retrieve a merchant GET /v1/merchants/{merchant_code}: sandbox, default_currency, country. Payment methods GET /v0.1/merchants/{merchant_code}/payment-methods.
Authentication: Authorization: Bearer <API key> on every call; one host (https://api.sumup.com) for live and sandbox merchants.
Webhooks: POST {"event_type": "CHECKOUT_STATUS_CHANGED", "id": "<checkout id>"} to the checkout's return_url; answer an empty 2xx quickly; retries after 1 min, 5 min, 20 min, 2 h; ignore unknown events; always verify through the API (the extension reads the checkout back).
Payment Widget: script https://gateway.sumup.com/gateway/ecom/card/v2/sdk.js, SumUpCard.mount({id, checkoutId, onResponse, locale, email, showZipCode, googlePay}); "success ... does not always mean the transaction was successful" (the extension reads the checkout back); HTTPS required in production.
Hosted Checkout: session valid for 30 minutes (the extension reuses a hosted page for 25 minutes only).
Refund path. The Refund guide page still shows the legacy POST /v0.1/me/refund/{txn_id}; the API reference (OpenAPI) and the official SDK use POST /v1.0/merchants/{merchant_code}/payments/{transaction_id}/refunds. The extension uses the API reference path.
No idempotency key for refunds. The API offers none. A refund call without a clear answer is never resent automatically; it is matched with the transaction's REFUND events (an event of the same amount that no other refund row claims) and marked failed with an order note after 24 hours if it never shows up.
HRK is still in the currency enum but was withdrawn from ISO 4217 on 2023-01-01; the extension does not offer it.
Webhooks are not signed by SumUp; they only trigger a read-back.
Not tested against a real SumUp account (tests never call a real provider). A first live payment with a small amount is recommended after set-up.
FAQ
Do I need a SumUp account?
Yes, your own SumUp merchant account and a secret API key. SumUp charges its own fees; payouts arrive like your card-reader sales.
Which payment methods do my customers see?
Cards always; Apple Pay, Google Pay and local methods (iDEAL, Bancontact, BLIK, EPS, Przelewy24, PayPal, PIX, Boleto ...) when SumUp has enabled them for your account and country. "Save and test connection" lists them.
Widget or Hosted Checkout?
The widget keeps the customer on your checkout page. Hosted Checkout sends the customer to SumUp's page with your SumUp branding and needs no script on your page. Both are read back the same way.
Can I take payments in several currencies?
SumUp checkouts are created in the order currency, and SumUp is offered only when that equals the checkout currency you set (normally your SumUp account currency). There is no conversion.
Why is my sandbox order "held"?
Sandbox merchants move no real money, so their payments are not booked as paid unless you tick "Accept sandbox payments".
A refund says "awaiting answer". SumUp did not answer clearly. SumUp's refund API has no idempotency key, so the extension never sends it a second time; it matches it with the transaction's refund events. Check the SumUp dashboard before refunding again.
Does it use OCMOD or change core files?
No. A payment extension with events and a cron job.
Server-side tracking for OpenCart 4.x and 3.0.x: purchases, refunds, add to cart, checkout, product views and leads go from your server to the free official GA4 Measurement Protocol, Meta Conversions API and TikTok Events API, deduplicated with the browser pixels through shared event ids. Consent-aware, SHA-256 hashed customer data, order status mapping, queue with retries, test mode and an event log. No paid relay, events only, no OCMOD.
The department store theme for OpenCart 4.x and 3.0.x: colour-coded departments, a department list beside the hero slider with two side promos, a big search with a department selector, tabbed product blocks, a 2 + 1 + 2 offer grid, deals of the day with real countdowns, a department showcase, a promotions band and a three-column product page with a sticky buy box. Four styles (Meridian, Harbor, Citrus, Night) and your own colours on top. Events only, no core changes.
Stop AI crawlers, scrapers, vulnerability scanners and unwanted countries before OpenCart 4.x or 3.0.x builds a page. Googlebot and Bingbot are verified by DNS and always allowed; 50+ bots get one switch each; country rules use free GeoIP databases and can close only the checkout; IP and network lists, rate limits, honeypots and the cart flood fix; a one-second challenge instead of a captcha; learning mode, dashboard, log, IP tester and Cloudflare export. No core changes, no OCMOD.