npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

medusa-plugin-quickpay

v1.0.0

Published

Medusa v2 QuickPay payment provider for hosted-checkout payments, refunds, and webhook reconciliation

Downloads

28

Readme

medusa-plugin-quickpay

A QuickPay payment provider for Medusa v2. One QuickPay account, three storefront payment options — credit card, MobilePay, and Anyday — all via QuickPay's hosted checkout window.

  • Full lifecycle: initiate, authorize, capture, refund, cancel
  • HMAC-SHA256 webhook verification with automatic key rotation
  • Idempotent webhook recording (safe under concurrent delivery)
  • Admin order widget: status, refunds, regenerate link, chargebacks
  • Currency-aware: DKK-only methods auto-hidden on non-DKK carts

Requirements

Install

pnpm add medusa-plugin-quickpay
# or: npm install medusa-plugin-quickpay

Setup

1. Add your QuickPay API key to your backend .env (QuickPay Manager → Settings → Users → your API user → copy the API key):

QUICKPAY_API_KEY=your_api_key_here

# Public backend URL — required so QuickPay can reach your webhook.
# Use an ngrok tunnel in development, your real domain in production.
BACKEND_PUBLIC_URL=https://api.yourdomain.com

2. Register the provider and plugin in medusa-config.ts:

import { Modules, defineConfig } from "@medusajs/framework/utils"

export default defineConfig({
  modules: [
    {
      key: Modules.PAYMENT,
      resolve: "@medusajs/payment",
      options: {
        providers: [
          {
            resolve: "medusa-plugin-quickpay/providers/quickpay",
            id: "quickpay",
            options: {
              apiKey: process.env.QUICKPAY_API_KEY,
            },
          },
        ],
      },
    },
  ],
  plugins: [{ resolve: "medusa-plugin-quickpay" }],
})

3. Enable the providers in Medusa Admin → Settings → Regions → your region:

| Provider ID | Method | Currencies | |-------------|--------|------------| | pp_creditcard_quickpay | Credit card | All | | pp_mobilepay_quickpay | MobilePay | DKK only | | pp_anyday_quickpay | Anyday (pay in installments) | DKK only |

Each customer lands on a hosted window where their chosen method is primary but they can still switch to another without abandoning the cart.

That's it — your storefront now shows the enabled QuickPay methods at checkout.

Configuration

Pass these in the provider options. Only apiKey is required.

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | QuickPay API key. | | callbackUrl | URL | ${BACKEND_PUBLIC_URL}/quickpay/webhook | Webhook endpoint. | | continueUrl | URL | ${STORE_CORS[0]}/checkout/quickpay/return | Redirect after success. | | cancelUrl | URL | ${STORE_CORS[0]}/checkout/quickpay/cancel | Redirect after cancel. | | baseUrl | URL | https://api.quickpay.net | QuickPay API base. Override only for proxies. | | acceptVersion | "v10" | "v11b" | "v10" | Accept-Version header. | | defaultPaymentMethods | string | creditcard,mobilepay,anyday-split | Hosted-window method tokens. | | defaultLanguage | string | — | ISO 639-1 code (e.g. da, en). | | brandingId | number | — | QuickPay branding ID for the hosted window. | | autoCapture | boolean | false | Capture immediately after authorize. | | syncRefunds | boolean | true | Use QuickPay's ?synchronized refund mode. | | testMode | "enforce_true" | "enforce_false" | "any" | enforce_false in prod, else any | Webhook test-mode guard. |

The webhook private key is not configured manually — the plugin fetches it from QuickPay and caches it, refreshing automatically on key rotation.

Storefront integration

The hosted-window flow needs three small additions to your storefront.

1. Payment button — redirect to the hosted-window URL from the session:

const isQuickPay = (id?: string) => id?.endsWith("_quickpay")

const QuickPayPaymentButton = ({ cart, notReady }: PaymentButtonProps) => {
  const session = cart.payment_collection?.payment_sessions?.find((s) =>
    isQuickPay(s.provider_id)
  )
  const linkUrl = (session?.data as any)?.quickpay_link_url as string | undefined

  return (
    <Button disabled={notReady || !linkUrl} onClick={() => (window.location.href = linkUrl!)}>
      Continue to QuickPay
    </Button>
  )
}

2. Hide DKK-only methods on non-DKK carts in your payment selector:

export const isPaymentMethodAvailableForCurrency = (
  providerId: string,
  currencyCode?: string | null
): boolean => {
  const dkkOnly = ["pp_mobilepay_quickpay", "pp_anyday_quickpay"]
  if (!currencyCode || !dkkOnly.includes(providerId)) return true
  return currencyCode.toUpperCase() === "DKK"
}

3. Return & cancel pages under app/[countryCode]/(checkout)/checkout/quickpay/:

  • return/ — QuickPay redirects here with payment_id and ps in the URL. Poll GET /quickpay/return?payment_id={id}&ps={ps} (forward bothps is required) and call Medusa's place-order flow when the outcome is completable.
  • cancel/ — call POST /quickpay/cancel with { payment_id, ps } to void the pending authorize and clean up, then return the customer to the cart.

API endpoints

| Endpoint | Purpose | |----------|---------| | POST /quickpay/webhook | QuickPay callbacks. HMAC-SHA256 verified; returns 401 on bad signature. | | GET /quickpay/return | Post-payment validation. Requires payment_id + matching ps. | | POST /quickpay/cancel | Customer cancel cleanup. Requires payment_id + matching ps. | | POST /admin/quickpay/payments/:id/regenerate-link | Admin — fresh hosted-window URL (pre-authorization only). |

/quickpay/return and /quickpay/cancel require ps (the QuickPay order id) to match the payment — this binds each request to a specific payment and stops the endpoints from being used to probe arbitrary payment ids.

Testing it locally

  1. In QuickPay Manager, enable Allow test transactions and activate at least one acquirer (e.g. Clearhaus test).
  2. Expose your backend: ngrok http 9000, and set BACKEND_PUBLIC_URL to the tunnel URL.
  3. Pay with QuickPay's test card: 1000 0000 0000 0008, any future expiry, any CVV.

Troubleshooting

| Symptom | Fix | |---------|-----| | Webhook callback Failed / no status in QuickPay | BACKEND_PUBLIC_URL isn't publicly reachable. Set it to a real/ngrok URL and restart. | | MobilePay / Anyday missing at checkout | They only show for DKK carts. Check the region currency, that the provider is enabled, and that the storefront filter is in place. | | Webhook signature fails (401) | The key refreshes automatically on rotation. Ensure nothing strips the raw request body before the webhook route. | | Payment stuck in pending | Confirm the webhook was delivered (QuickPay dashboard → payment → operations) and the backend is reachable. | | Cannot find module '@medusajs/utils' | Run pnpm add @medusajs/utils in your backend project. |

Programmatic exports

For advanced use the package exports its provider class and pure helpers (QuickPayClient, verifyQuickPaySignature, validateReturnOutcome, derivePaymentTotals, resolveQuickPayOptions, and more) from the package root:

import { QuickPayClient, verifyQuickPaySignature } from "medusa-plugin-quickpay"

Development

pnpm install
pnpm build      # compile (medusa plugin:build)
pnpm dev        # watch mode
pnpm test       # run the test suite

License

MIT