Medusa v2 Paystack plugin with per-store publishable key settings, storefront helpers, and an optional verified payment provider.
Medusa v2 plugin for Paystack with:
Helper mode only needs the Paystack publishable key.
It gives you:
In helper mode, the browser callback is only a UX signal. The plugin does not mark a Medusa order as paid from the client callback alone.
Verified mode uses the same browser checkout flow, but also registers the optional payment provider with a Paystack .
In verified mode:
npm install medusa-paystack-plugin
Register the plugin in your Medusa app:
1module.exports = defineConfig({2 plugins: [3 {4 resolve: "medusa-paystack-plugin",5 options: {},6 },7 ],8})
If you want verified Medusa payment completion, also register the provider:
1module.exports = defineConfig({2 plugins: [3 {4 resolve: "medusa-paystack-plugin",5 options: {6 secret_key: process.env.PAYSTACK_SECRET_KEY,7 },8 },9 ],10 modules: [11 {12 resolve: "@medusajs/medusa/payment",13 options: {14 providers: [15 {16 resolve: "medusa-paystack-plugin/providers/paystack",17 id: "paystack",18 options: {19 secret_key: process.env.PAYSTACK_SECRET_KEY,20 },21 },22 ],23 },24 },25 ],26})
After the plugin is installed, open the store details page in Medusa Admin. A widget is injected into .
Save the store-specific publishable key there. The widget stores it in:
store.metadata.paystack_publishable_key
Returns:
1{2 "store_id": "store_123",3 "publishable_key": "pk_test_xxx",4 "verified_mode_enabled": true5}
If your Medusa installation has more than one store, pass .
Returns a minimal HTML page that loads Paystack InlineJS and starts checkout inside a mobile webview or popup-style browser window.
Supported query params:
On success, cancel, or error the page:
Import the helper:
1import {2 buildPaystackReference,3 buildPaystackWebviewUrl,4 createPaystackSuccessHandler,5 fetchPaystackConfig,6 launchPaystackCheckout,7} from "medusa-paystack-plugin/frontend"
1const config = await fetchPaystackConfig({2 baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL,3 storeId: "store_123",4})56const onSuccess = createPaystackSuccessHandler({7 mode: config.verified_mode_enabled ? "verified" : "helper",8 cartId: cart.id,9 sdk,10 redirectUrl: `${window.location.origin}/checkout/complete`,11})1213await launchPaystackCheckout({14 key: config.publishable_key,15 email: cart.email!,16 amount: paymentSession.amount,17 currency: paymentSession.currency_code,18 reference:19 (paymentSession.data as { paystack_reference?: string })?.paystack_reference ??20 buildPaystackReference(),21 metadata: {22 cart_id: cart.id,23 },24 successHandler: onSuccess,25})
1const webviewUrl = buildPaystackWebviewUrl({2 baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL,3 callbackUrl: "myapp://checkout/complete",4 currency: cart.currency_code,5 email: cart.email!,6 amount: cart.total,7 metadata: {8 cart_id: cart.id,9 },10 storeId: "store_123",11})
Load that URL in your mobile webview and listen for the message payload or the redirect to your callback URL.
1npm run typecheck2npm test3npm run build