Skip to content
GatewayChanger
Available Magento 2.4.6+ / Mage-OS · PHP 8.1+

GatewayChanger for Magento 2

The GatewayChanger_Payment module gives a Magento 2 or Mage-OS store the same two roles as the WooCommerce plugin: seller and charger. It speaks exactly the same protocol, with the same field names, endpoints and error codes, so a Magento store can sell to a WooCommerce charger and the other way round. The same is true of the PrestaShop, OpenCart and Zen Cart plugins now in build.

Status: available (v1, Luma checkout). Built and verified end-to-end against Mage-OS 3.5 (Magento 2.4.9) on PHP 8.4, Luma checkout first. Hyvä compatibility is a documented follow-up.

Requirements

Magento 2.4.8 / Mage-OS (2.4.7 should work; not yet part of the test matrix)
PHP 8.3 or 8.4 with ext-sodium
Checkout Luma (Knockout) checkout; Hyvä is a follow-up
Transport HTTPS, and /rest/V1/gatewaychanger/* reachable from the internet
Cron Magento cron running — it sends the five-minute heartbeat

Install

Download the module zip from Plugins in the dashboard (it carries the current version and its SHA-256 checksum) and unzip it in the Magento root — the archive already ships at app/code/GatewayChanger/Payment/ and contains an INSTALL.md with these same steps:

unzip gatewaychanger-magento2-latest.zip -d .
bin/magento module:enable GatewayChanger_Payment
bin/magento setup:upgrade
bin/magento setup:di:compile        # production mode
bin/magento cache:flush

Composer works too, once the package is published: composer require gatewaychanger/module-payment, then the same commands from module:enable onwards.

Connect the shop

Stores → Configuration → Sales → Payment Methods → GatewayChanger, or from the command line:

bin/magento gatewaychanger:connect --key=shk_test_…

The module generates its Ed25519 key pair, registers the public key with the SaaS, and reports its roles and capabilities. The configuration page then shows the shop name, the environment badge, the verification status and the last heartbeat. Our verification probe calls https://your-store.example/rest/V1/gatewaychanger/capabilities; until it succeeds the shop cannot act as a charger.

Flush the configuration cache after changing anything (bin/magento cache:flush config).

Everything else is configured in the dashboard

The Magento configuration screen only holds the connection. Roles, checkout settings, charger settings and payment processors are managed in the GatewayChanger dashboard (Shops → your shop) and pulled by the module: we ping POST /rest/V1/gatewaychanger/sync whenever you save, the module fetches GET /api/v1/shops/self/config, applies it atomically, tests every enabled processor and reports the result. The shop's Connection & plugin tab shows the saved version, the applied version, the last sync ping and any errors, plus a Sync now button.

Seller role

On the dashboard's Seller settings tab:

  • title and description shown on the checkout step;
  • the consent label, and the CMS page holding your terms;
  • payment mode — embed (default) or redirect;
  • the order status used for authorizations;
  • what to do when no route is available.

The checkout renderer draws the consent checkbox and the charger's iframe, creates the session through /rest/V1/gatewaychanger/checkout/session (guest and customer variants), re-routes on finalize when the total changed, and passes the session id, the payment token and the consent hash in the payment additional_data.

Authorize, capture, void and refund run through the standard Magento gateway command pool, so invoices and credit memos behave normally. The order view shows a GatewayChanger block with the charger, the processor, the references and a link to the decision.

Charger role

On the dashboard's Charger & processors tab: turn on the charger role, set the legal name, and add at least one processor.

  • Test processor — no credentials, deterministic outcomes, ideal for the first run.
  • Stripe — publishable key, secret key and webhook signing secret, in test mode first. The webhook address to add in Stripe is shown on the payment account in the dashboard — see payment notifications.
  • Catalog entries with no plugin implementation yet (PayPal, Braintree, Authorize.net, Mollie, Adyen, Checkout.com) are shown as "coming soon".

Credentials are end-to-end encrypted. The module generates an X25519 key pair on activation and reports the public half; your browser seals each secret to that key (crypto_box_seal) and only sb1.<fingerprint>.<ciphertext> is stored and synced. The module decrypts it and writes it into Magento's own encrypted configuration. GatewayChanger has no private key and the API refuses any credential that arrives unsealed. Reinstalling the module creates a new key, so values sealed to the old one are flagged re-enter required. A processor only routes once the module has reported it connected.

Charger endpoints are exposed through etc/webapi.xml as anonymous resources whose real authentication is the GatewayChanger request signature and grant verification. Sessions live in the gatewaychanger_session table. The embed page is /gatewaychanger/embed/index and the hosted pay page is /gatewaychanger/pay/index; both are CSRF-exempt controllers with the correct frame-ancestors policy.

Each charge creates a mirror order from a programmatic quote with a single virtual line — "GatewayChanger payment on behalf of {seller}" — invoiced on capture and credit-memoed on refund.

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 Magento the address has this shape, where {id} is the payment account's id (stripe, adyen, paypal, …):

https://your-store.example/gatewaychanger/webhook/receive/processor/{id}

The older Stripe address, /gatewaychanger/webhook/stripe, 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 store runs version 1.5.0 of the module or newer.

  • Once notifications arrive, the account says Last notification received …. If the store 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 store gives Mollie the address with every payment. Mollie can only reach a public https address; on a local test store the module 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 store. GatewayChanger never receives them and never holds the secret.

Testing

Use the test cards with the test processor, and drive the flow as described in the getting started guide. A useful pairing while you develop: Magento as seller against a WooCommerce charger, then the reverse, which proves both sides of the protocol.

Troubleshooting

setup:di:compile fails after an update. Clear generated/ and recompile.

The probe cannot reach the store. Check that /rest/V1/gatewaychanger/capabilities returns JSON to an anonymous request, that no WAF blocks it, and that the base URL in the shop record matches Magento's web/secure/base_url.

No heartbeat. Magento cron is not running, or the gatewaychanger cron group is disabled. Fifteen minutes of silence removes the shop from routing.

The checkout step does not render. Clear pub/static and the full page cache after enabling the module, and check the browser console for a failing RequireJS dependency.