Skip to content
GatewayChanger
In build OpenCart 4.0 – 4.1 · PHP 8.1+

GatewayChanger for OpenCart

One extension, both roles. An OpenCart store can sell (route its checkout to a partner shop that charges the card) and charge (accept routed payments with its own processor accounts) — often both at the same time. It speaks the same protocol as the WooCommerce plugin, with the same field names and error codes, so an OpenCart store can sell to a WooCommerce charger and the other way round.

Status: in build — not yet verified against a live store (2026-09-22). We are writing this extension now and targeting version 1.0.0. It has not run on a production OpenCart, there is no zip on the Plugins page yet, and it has not been through the end-to-end conformance suite that the WooCommerce and Magento plugins pass. This page is the build target — published early so you can see exactly what you would be getting and tell us now if it does not fit your store. It is not a release note. We would rather show you a date than a checkmark we cannot back: when the first build passes the gate, the date and the version appear here and in the changelog.

Requirements

OpenCart 4.0.0 – 4.1.0.4. The extension ships both getMethod() and getMethods(), so it works either side of the 4.0.2.0 rename.
OpenCart 3.x Not supported in 1.0.0. 3.x differs in the directory layout, the autoloader, the event system and the payment model; it is a separate port, not a flag.
PHP 8.1 or newer with ext-sodium; developed on 8.4
Checkout The standard OpenCart checkout. Embedded and redirect modes both.
Database Its own *_gwc_* tables (sessions, nonces, callbacks, events). No core table is altered.
Cron A system cron every five minutes calling the extension's token-protected cron route — it sends the heartbeat.
Transport HTTPS on both stores, and the extension's catalog routes reachable from the internet without a redirect.

Install

  1. Download gatewaychanger.ocmod.zip from Plugins in your GatewayChanger dashboard. The card shows the version and its SHA-256 checksum. (No package yet — see the status note above.)
  2. Do not rename the file. OpenCart takes the extension's folder name and its install code straight from the filename: gatewaychanger.ocmod.zip becomes extension/gatewaychanger/. A renamed zip installs into the wrong place and nothing finds it.
  3. Extensions → Installer → Upload, pick the zip, and let the paged extraction finish.
  4. Extensions → Extensions → choose Payments in the type filter, find GatewayChanger, press the green + to install it, then Edit.
  5. If the edit page says Could not load controller, give your user group access: System → Users → User Groups → your group, tick Access and Modify for extension/gatewaychanger/payment/gatewaychanger.

Installing creates the extension's own tables, registers its checkout event and its cron entry, and adds two order statuses so a routed order never has to borrow a status that means something else in your reports.

Updating is uninstall + re-upload. The OpenCart installer refuses to write over an existing extension/gatewaychanger/ directory and never overwrites a file, so "install on top" silently leaves you on the old code. Uninstall from Extensions → Installer first; your tables and settings survive it.

Connect the shop

Register the shop in the dashboard first (Shops → Add shop, platform OpenCart), then on the extension's edit page, under Connection:

  1. Leave the API base at https://gatewaychanger.com/api/v1 unless you were told otherwise.
  2. Paste the shop key from the dashboard — shk_test_… in the test environment, shk_live_… in live.
  3. Press Connect, then Save.

The extension generates an Ed25519 key pair (the private key never leaves your server), registers the public key, and reports its roles and capabilities. You then see the shop name, a TEST MODE or LIVE badge, the verification status and the time of the last heartbeat.

We call your store back at https://your-store.example/index.php?route=extension/gatewaychanger/payment/gatewaychanger.capabilities — the exact URL is printed on the settings page — and the shop stays pending_verification until that returns the expected JSON. The probe does not follow redirects, so the base URL has to be the final one: no apex↔www hop, no http→https hop.

One OpenCart quirk worth knowing: a mistyped route and maintenance mode both return HTTP 200 with an HTML page, so "it loads in my browser" proves nothing. Check with curl and look for JSON.

Key rotation generates a new pair and keeps the previous public key valid for seven days, so in-flight sessions do not break.

Everything else is configured in the dashboard

The extension's own screen is only about the connection. Roles, checkout texts, charger settings and payment processors live in the GatewayChanger dashboard (Shops → your shop), and the extension pulls them: we ping it whenever you save, it fetches GET /api/v1/shops/self/config, applies the document atomically, tests every enabled processor and reports back. The settings page shows the applied configuration read-only with a Managed in the GatewayChanger dashboard note.

The shop detail page shows the state of that loop under Connection & plugin: the version you saved, the version the extension applied, the last sync ping and its result, a Sync now button, and any errors it reported.

Seller role

Turn on the seller role and the seller settings in the dashboard (Seller settings tab).

Setting Notes
Title / description What the customer sees in the payment list — for example "Credit / debit card".
Consent label Defaults to the template we send; keep who charges, on whose behalf, and the amount.
Terms page The information page linked from the consent line. It needs the payment-processing clause.
Payment mode embed (recommended — the customer stays on your checkout) or redirect.
When no route is available Hide the payment method, or charge on this shop itself.
Order status for authorizations Usually the Awaiting GatewayChanger payment status until the charge is captured.

At checkout the extension renders the payment widget, creates a session, shows the consent checkbox and the charger's iframe, re-routes on finalize if the total changed, and confirms the order with the amount the charger actually took. Order totals in OpenCart are stored in the store's base currency, so the extension converts with the order's frozen currency value — the rate at the time of the order, not today's — before it ever sends an amount to us. The order history comment records the routing:

Charged €24.99 via Shop B Ltd (Stripe) — ref pi_3Q1… — routed by rule "EU cards over €50" (decision dec_01J8…)

OpenCart has no refund API of its own, so the extension adds a Refund action to the order page; it is forwarded to the charger, executed at the processor, and reported back to both sides.

Charger role

In the dashboard, open the Charger & processors tab: turn on the charger role, set the legal name exactly as it should appear in the consent sentence and on receipts, then add at least one processor.

Credentials are end-to-end encrypted. On activation the extension generates an X25519 key pair alongside its signing key and reports the public half. When you type a secret in the dashboard, your browser seals it to that key (crypto_box_seal) and only the ciphertext — sb1.<fingerprint>.<…> — is stored and synced. OpenCart has no secret store, so the extension keeps the opened value in its own table under a second layer of encryption, never in oc_setting where a settings export would carry it away. The API rejects any credential that is not sealed, and the value is never shown again.

Test processor

No credentials. Deterministic outcomes by card number — see test cards. Use it for the first end-to-end run and for continuous integration; it is free and never talks to a third party.

Stripe

  1. In the Stripe dashboard, switch to test mode and open Developers → API keys.
  2. In the GatewayChanger dashboard, Charger & processors → Add a processor → Stripe, and paste the publishable key (pk_test_…) and the secret key (sk_test_…).
  3. Add a webhook endpoint pointing at the URL the extension's settings page prints for you, subscribe to payment_intent.*, charge.refunded and charge.dispute.*, and paste the signing secret (whsec_…) into the same dialog.
  4. Choose the capture method (automatic or manual) and, optionally, a statement descriptor suffix.
  5. Repeat with live keys when you go live — and only then.

Both keys are encrypted in your browser before they are sent and can only be decrypted inside your OpenCart installation. GatewayChanger never receives them in readable form, never proxies a call to Stripe, and has no way to charge on your behalf. After you save, the extension tests the keys and the dashboard shows connected or the exact error — and routing skips a processor that is not connected.

Mirror orders

Every charge creates an order in your own store with a single line — "Payment on behalf of {seller} order {number}" — so your bookkeeping, tax reporting and refund workflow keep working the way they already do. The seller's order id and the decision id are stored on it. Order-status changes in OpenCart have side effects (stock, coupons, customer email), so the extension only ever moves a status it owns, and only once per event.

Testing the whole flow

  1. Connect two stores, one seller and one charger, both in the test environment.
  2. Enable the test processor at the charger.
  3. Create one catch-all rule pointing at the charger, with "seller charges itself" as a second candidate.
  4. Check out with 4111 1111 1111 1111 → captured.
  5. Retry with 4000 0000 0000 0002 → declined; the iframe resets and asks for another card, and no failover happens.
  6. Set the cart total to exactly 44.00 → processor_error; the seller re-routes with the charger excluded and the second candidate takes it.
  7. Refund half the order from the order page and watch the refund appear on the mirror order and in the ledger.

Run one order in a second currency as well. The base-currency total is the classic OpenCart double-charge, and it is the one thing worth proving on your own data before you go live.

Troubleshooting

Every payment method disappeared at checkout, not just ours. That is the getMethod / getMethods trap: OpenCart probes the model as a property, so one wrong method name throws and wipes the whole list. Our extension ships both names; if you see it after installing something else, that extension is the one to look at.

Signatures fail, or a callback is rejected. OpenCart runs htmlspecialchars() over every superglobal, including $_SERVER, which corrupts base64 and JSON read through the request object. The extension reads raw input and raw headers for exactly this reason — but a proxy that rewrites headers will still break it.

"Session expired" at checkout. Grants live 30 minutes. A checkout left open longer needs a new session; reloading the payment step is enough.

The iframe is blank. The embed page restricts frame-ancestors to the seller origin. Mismatched www/apex or http/https between the configured base URL and the page the customer is on will block it.

Heartbeats stop. OpenCart's own cron list runs at most hourly, which is not often enough — the extension needs a five-minute system cron on its token-protected route, and the settings page prints the exact crontab line. Fifteen minutes without a heartbeat removes the shop from routing.

The admin page 404s after a fresh install. The user-group permission from install step 5 is missing, or the installer row did not land — uninstall from Extensions → Installer and re-upload rather than copying files by hand.

Where this stands

  • Written and reviewed against the OpenCart 4 extension contract (installer, event system, payment model, cron) and the same protocol document the other plugins implement.
  • Not yet installed on a live store, not yet through the four-part conformance gate: the independent signing client, the unit tests, a cross-platform end-to-end run (OpenCart as seller against a WooCommerce charger, then the reverse), and a field-by-field diff of GET /capabilities against the WooCommerce document.
  • Out of scope for 1.0.0: OpenCart 3.x, self-update from the dashboard, and the dashboard's SSH auto-installer. The first release is download-and-upload.
  • If you run OpenCart and want to be in the first round of real installs, mail [email protected] — we would rather find the surprises with you than after you.

Plugins marked “In build” are published here only once a real test store has routed a real order end to end.


Something wrong or missing on this page? Tell us and we will fix it.