Skip to content
GatewayChanger
Available WordPress 6.9+ · WooCommerce 10.9+ · PHP 8.1+

GatewayChanger for WooCommerce

One plugin, both roles. A WooCommerce 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 is the reference implementation: the Magento 2 module is verified against it, and the PrestaShop, OpenCart and Zen Cart plugins now in build are written against the same protocol and tested against this one.

Status: available (v1). Built and verified end-to-end on WooCommerce 11.1 / WordPress 7.1 (block and classic checkout, embedded and redirect modes, refunds, simulated 3-D Secure). Stripe support is implemented but not yet exercised with live keys. Screens and labels may still move before the first public release; the protocol they speak will not. The changelog records every change.

Requirements

WordPress 6.9 or newer (tested up to 7.1)
WooCommerce 10.9 or newer (tested up to 11.1)
PHP 8.1 or newer, with sodium (WordPress ships a compatible fallback)
Checkout Block checkout and classic checkout are both supported
HPOS Compatible — declared, and tested with high-performance order storage on and off
Transport HTTPS on both stores, and the WordPress REST API reachable from the internet

Install

  1. Download the plugin zip from Plugins in your GatewayChanger dashboard (the page shows the current version and its SHA-256 checksum).
  2. Plugins → Add New → Upload Plugin, choose the zip, install, activate.
  3. A new settings screen appears at WooCommerce → Settings → Payments → GatewayChanger.

Activation creates three small tables (wp_gwc_sessions, wp_gwc_nonces, wp_gwc_callbacks) and schedules the heartbeat through Action Scheduler. Uninstalling removes them.

Connect the shop

In 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.

The plugin 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/wp-json/gwc/v1/capabilities to verify it. Until that succeeds the shop is pending_verification and cannot be a charger. If it fails, check for security plugins, firewalls, basic auth, or a base_url that does not match the store's real address, then press Re-verify.

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 plugin screen is only about the connection. Roles, checkout texts, charger settings and payment processors live in the GatewayChanger dashboard (Shops → your shop), and the plugin 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 plugin shows the applied settings 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 plugin applied, the last sync ping and its result, a Sync now button, and any errors the plugin reported.

Seller role (Shop A)

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 method 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 page linked from the consent line. Make sure it has 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 on hold until the charge is captured.

At checkout the plugin creates a session, renders the consent checkbox and the charger's iframe, re-routes on finalize if the total changed, and only then places the order. Order meta records the decision, the charger, the processor, the references and the card brand with the last four digits, and an order note summarises the routing:

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

Refunds from the WooCommerce refund UI are sent to the charger, which refunds at the processor and reports the event back. Captures and voids for authorized orders are available on the order screen.

Charger role (Shop B)

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 plugin 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. Only your store can open it; the API rejects any credential that is not sealed, and the value is never shown again. Reinstalling the plugin creates a new key, so anything sealed to the old one is flagged re-enter required.

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. Register Stripe's payment notifications: add the address shown on the payment account in the dashboard as a webhook endpoint in Stripe (Developers → Webhooks), send it the events listed there, and paste its signing secret (whsec_…) into the same payment account. See payment notifications below.
  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 WordPress 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 plugin tests the keys and the dashboard shows connected or the exact error — and routing skips a processor that is not connected.

Payment notifications

Some outcomes are only known after the checkout: a refund that completes a day later, a capture the bank rejects, a chargeback, a refund you make in the provider's own dashboard. The provider reports them to the shop that took the payment — in the provider's dashboard this is usually called a webhook — and that shop passes each one on to GatewayChanger and to the shop that sold the order.

Register the address shown on your payment account in the dashboard (Shops → your shop → Payment accounts). Each account lists the exact address, the notifications to send to it and the secret to paste back into the same account, each with a copy button. For WooCommerce the address has this shape, where {id} is the payment account's id (stripe, adyen, paypal, …):

https://your-store.example/?wc-api=gatewaychanger_webhook&processor={id}

The older Stripe address, ?wc-api=gatewaychanger_stripe_webhook, keeps working as an alias of …&processor=stripe, so an endpoint you registered before does not need to change. The address appears on the payment account once the shop runs version 1.5.0 of the plugin or newer.

  • Once notifications arrive, the account says Last notification received …. If the shop keeps turning them away for more than an hour — almost always a signing secret that is not the one in the provider's dashboard — the account is marked, Needs you lists it, and the button next to it opens the field that fixes it.
  • Mollie needs nothing registered: the shop gives Mollie the address with every payment. Mollie can only reach a public https address; on a local test shop the plugin asks Mollie for each outcome instead.
  • A payment whose outcome is still unknown 14 days later is listed in Needs you, so you can check it in the provider's dashboard.
  • Notifications only ever reach your shop. GatewayChanger never receives them and never holds the secret.

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 number and the decision id are stored on it.

Other charger settings

All in the dashboard, on the same tab:

  • Default capture mode, and whether partial refunds are allowed.
  • The mirror-order line label (placeholders {seller} and {number}).
  • Logging level (under Seller settings), plus a diagnostics report in the plugin you can share with us.

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 in WooCommerce and watch the refund appear on the mirror order and in the ledger.

Troubleshooting

The payment method does not appear. The gateway hides itself when the shop is not connected, the seller role is off, or no candidate supports the cart currency. Enable "show an error instead" while you debug.

"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. Action Scheduler is not running — usually because WP-Cron is disabled without a system cron replacing it. Fifteen minutes without a heartbeat removes the shop from routing.

Orders stuck pending. The charger's callback did not arrive. The order screen has a Sync with charger action that re-reads the charge state, and pending callbacks are retried with backoff.