@zahls/medusa-plugin
v0.0.1
Published
zahls.ch payment provider for Medusa v2 (TWINT, cards, PostFinance).
Downloads
57
Maintainers
Readme
@zahls/medusa-plugin
zahls.ch payment provider for Medusa v2.
This plugin lets a Medusa application create and manage zahls.ch Gateway checkouts from the backend. It supports:
- Hosted checkout, where the customer is redirected to the zahls.ch payment page (
session.data.link) - Swiss payment methods such as TWINT, cards, and PostFinance
- Captures for authorized / reserved transactions
- Refunds through zahls.ch transactions
- Medusa's built-in payment webhook route for asynchronous status updates, with optional HMAC signature verification
The plugin never handles raw card data directly. zahls.ch credentials remain on the Medusa backend.
Compatibility
- Medusa v2.18.x
- zahls.ch Gateway API
Install
npm install @zahls/medusa-pluginConfigure Medusa
Register the plugin and payment provider in medusa-config.ts:
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
plugins: [
{
resolve: "@zahls/medusa-plugin",
options: {},
},
],
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@zahls/medusa-plugin/providers/zahls",
id: "zahls",
options: {
apiKey: process.env.ZAHLS_API_KEY,
instance: process.env.ZAHLS_INSTANCE,
webhookSecret: process.env.ZAHLS_WEBHOOK_SECRET,
successRedirectUrl: process.env.ZAHLS_SUCCESS_URL,
failedRedirectUrl: process.env.ZAHLS_FAILED_URL,
cancelRedirectUrl: process.env.ZAHLS_CANCEL_URL,
},
},
],
},
},
],
})After the application starts, enable zahls.ch for the relevant region in Medusa Admin → Settings → Regions. Per Medusa's payment-provider model, the resulting provider identifier is pp_zahls_zahls when the service identifier is zahls and the configured provider id is zahls.
Configuration Options
| Option | Required | Description |
| --- | --- | --- |
| apiKey | Yes | Instance API secret from zahls.ch → API & Integrations. Keep it server-side. |
| instance | Yes | Instance name (example for example.zahls.ch). |
| webhookSecret | Yes | Signing key for X-Webhook-Signature verification. Webhooks are rejected without it. |
| successRedirectUrl | No | Storefront URL after a successful payment. |
| failedRedirectUrl | No | Storefront URL after a failed payment. |
| cancelRedirectUrl | No | Storefront URL after the customer cancels. |
| skipResultPage | No | Skip the zahls.ch result page (default true). |
Auth uses the X-API-KEY header (recommended by the zahls.ch / Payrexx REST API).
Hosted Checkout
The plugin creates a zahls.ch Gateway and stores the returned checkout link in the payment-session data. The storefront should redirect the customer to that URL:
const link = paymentSession.data?.link
if (typeof link === "string") {
window.location.href = link
}Use backend / webhook state as the source of truth. The storefront should not treat the redirect alone as proof of payment success.
When available, customer name, email, company, and billing address from the Medusa payment context are prefilled on the Gateway.
Webhooks
Medusa provides a built-in webhook listener route for payment providers at:
/hooks/payment/[identifier]_[provider]For this plugin, with service identifier zahls and provider id: "zahls", add this URL in the zahls.ch merchant backend (Webhooks), with JSON content type:
https://your-medusa-backend.com/hooks/payment/zahls_zahlsThe plugin verifies X-Webhook-Signature when webhookSecret is set, loads the Gateway from zahls.ch, maps the status to a Medusa payment action, and returns the payment session reference (referenceId) back to Medusa.
| zahls.ch status | Medusa webhook action |
| --- | --- |
| confirmed | captured |
| authorized / reserved | authorized |
| waiting | pending_authorization |
| cancelled | canceled |
| failed / declined | failed |
referenceId on the Gateway is set to the Medusa payment session id so webhooks can resolve the session.
What the Plugin Stores
The payment-session data returned by the provider includes:
id— zahls.ch Gateway idhashlink— hosted checkout URLreferenceId— Medusa payment session idstatusamountandcurrencytransactionIdwhen availablelastRefundIdafter a refund
Current Behavior and Limitations
- Checkout is hosted-redirect only. There is no embedded card widget mode.
authorizePaymentchecks the remote zahls.ch Gateway status rather than performing a separate authorization step.capturePaymentsucceeds immediately when the Gateway is alreadyconfirmed. Forauthorized/reserved/uncaptured, it calls zahls.ch capture (and falls back to charge if needed).updatePaymentrecreates the Gateway when amount or currency changes before payment.- Refunds require a successful zahls.ch transaction id on the session.
webhookSecretis required; unsigned webhooks are rejected.
Sandbox Checklist
- Create a Gateway and complete one successful hosted checkout (e.g. TWINT or card).
- Confirm the storefront redirect to
session.data.linkworks. - Verify at least one webhook-driven status update to Medusa.
- Verify
X-Webhook-Signaturerejection when the secret is wrong. - Capture an authorized / reserved payment if your zahls.ch flow supports it.
- Verify one full refund and one partial refund.
- Verify canceled and failed checkouts map cleanly back into Medusa session state.
Local Development
npm run build
npm run devnpm run test:unit
npm run test:integration:modulesModule integration tests need PostgreSQL (DB_HOST, DB_USERNAME, DB_PASSWORD, DB_PORT). Defaults: localhost:5432, user postgres.
Publish locally with npx medusa plugin:publish, then in a Medusa app:
npx medusa plugin:add @zahls/medusa-plugin