Skip to content
GatewayChanger

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.

Status: in development. The plugin is being built and tested against live WooCommerce stores. 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 your GatewayChanger dashboard.
  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.

Seller role (Shop A)

Enable Seller in the plugin and enable the GatewayChanger payment method in WooCommerce → Settings → Payments.

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 show an error.
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)

Enable Charger, set the legal name exactly as it should appear in the consent sentence and on receipts, then switch on at least one processor.

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. Copy the publishable key (pk_test_…) and the secret key (sk_test_…) into the plugin.
  3. Add a webhook endpoint pointing at https://your-store.example/?wc-api=gatewaychanger_stripe_webhook, subscribe to payment_intent.*, charge.refunded and charge.dispute.*, and paste the signing secret (whsec_…) into the plugin.
  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 stay in your WordPress installation. GatewayChanger never receives them, never proxies a call to Stripe, and has no way to charge on your behalf.

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

  • Default capture mode, and whether partial refunds are allowed.
  • Mirror-order status mapping and the line label.
  • Logging level, and a diagnostics report 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.