{"id":41181132,"url":"https://github.com/alexasomba/medusa-v2-paystack-payment-plugin","last_synced_at":"2026-01-22T20:06:06.113Z","repository":{"id":302250552,"uuid":"1011776106","full_name":"alexasomba/medusa-v2-paystack-payment-plugin","owner":"alexasomba","description":"A robust Paystack payment integration plugin for Medusa v2.8.6+ that provides seamless payment processing using Paystack's payment infrastructure with support for the new payment session lifecycle.","archived":false,"fork":false,"pushed_at":"2025-07-02T11:53:03.000Z","size":444,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-19T15:06:57.286Z","etag":null,"topics":["medusa","medusa-plugin","medusa-plugin-payment","medusa-v2","payment","paystack","paystack-api","paystack-sdk","plugin"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexasomba.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-01T10:19:52.000Z","updated_at":"2025-07-02T11:53:06.000Z","dependencies_parsed_at":"2025-07-02T12:44:45.331Z","dependency_job_id":null,"html_url":"https://github.com/alexasomba/medusa-v2-paystack-payment-plugin","commit_stats":null,"previous_names":["alexasomba/medusa-paystack-plugin-v2","alexasomba/medusa-v2-paystack-payment-plugin"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/alexasomba/medusa-v2-paystack-payment-plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexasomba%2Fmedusa-v2-paystack-payment-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexasomba%2Fmedusa-v2-paystack-payment-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexasomba%2Fmedusa-v2-paystack-payment-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexasomba%2Fmedusa-v2-paystack-payment-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexasomba","download_url":"https://codeload.github.com/alexasomba/medusa-v2-paystack-payment-plugin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexasomba%2Fmedusa-v2-paystack-payment-plugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28670366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T19:36:09.361Z","status":"ssl_error","status_checked_at":"2026-01-22T19:36:05.567Z","response_time":144,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["medusa","medusa-plugin","medusa-plugin-payment","medusa-v2","payment","paystack","paystack-api","paystack-sdk","plugin"],"created_at":"2026-01-22T20:06:06.009Z","updated_at":"2026-01-22T20:06:06.103Z","avatar_url":"https://github.com/alexasomba.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Medusa v2 Paystack Payment Plugin\n\nA robust Paystack payment integration plugin for **Medusa v2.8.6+** that provides seamless payment processing using Paystack's payment infrastructure with support for the new payment session lifecycle.\n\n## ✨ Features\n\n- 🚀 **Complete Payment Flow**: Initialize, authorize, capture, and refund payments\n- 🔐 **Secure Transactions**: Built-in webhook verification and secure API communication  \n- 🌍 **Multi-Currency Support**: NGN, USD, GHS, ZAR with automatic currency conversion\n- 📱 **Modern Payment Experience**: Paystack Popup integration with retry mechanisms\n- 🎯 **Medusa v2 Compatible**: Full support for new payment session lifecycle\n- 🔄 **Session Management**: Intelligent session refresh and expiry handling\n- 🛡️ **Error Handling**: Comprehensive error handling with user-friendly messages\n\n## 📦 Installation\n\n### 1. Install the Plugin\n\n```bash\nnpm install @alexasomba/medusa-paystack-plugin-v2\n# or\nyarn add @alexasomba/medusa-paystack-plugin-v2\n```\n\n### 2. Environment Configuration\n\nAdd these environment variables to your `.env` file:\n\n```env\n# Paystack Configuration\nPAYSTACK_SECRET_KEY=sk_test_your_secret_key_here\nPAYSTACK_PUBLIC_KEY=pk_test_your_public_key_here\nPAYSTACK_WEBHOOK_SECRET=your_webhook_secret_here\n```\n\n### 3. Medusa Configuration\n\nConfigure the plugin in your `medusa-config.ts`:\n\n```typescript\nimport { loadEnv, defineConfig } from '@medusajs/framework/utils'\n\nloadEnv(process.env.NODE_ENV || 'development', process.cwd())\n\nmodule.exports = defineConfig({\n  projectConfig: {\n    databaseUrl: process.env.DATABASE_URL,\n    http: {\n      storeCors: process.env.STORE_CORS!,\n      adminCors: process.env.ADMIN_CORS!,\n      authCors: process.env.AUTH_CORS!,\n      jwtSecret: process.env.JWT_SECRET || \"supersecret\",\n      cookieSecret: process.env.COOKIE_SECRET || \"supersecret\",\n    }\n  },\n  modules: [\n    {\n      resolve: \"@medusajs/payment\",\n      options: {\n        providers: [\n          {\n            resolve: \"@alexasomba/medusa-paystack-plugin-v2\",\n            id: \"paystack\",\n            options: {\n              secret_key: process.env.PAYSTACK_SECRET_KEY,\n              public_key: process.env.PAYSTACK_PUBLIC_KEY,\n              webhook_secret: process.env.PAYSTACK_WEBHOOK_SECRET,\n            },\n          },\n        ],\n      },\n    },\n  ],\n})\n```\n\n### 4. Build and Start\n\n```bash\nnpm run build\nnpm run dev\n```\n\n## ⚙️ Configuration\n\n### Environment Variables\n\n| Variable | Description | Required | Example |\n|----------|-------------|----------|---------|\n| `PAYSTACK_SECRET_KEY` | Your Paystack secret key | ✅ | `sk_test_...` |\n| `PAYSTACK_PUBLIC_KEY` | Your Paystack public key | ✅ | `pk_test_...` |\n| `PAYSTACK_WEBHOOK_SECRET` | Webhook secret for verification | ⚠️ Recommended | `whsec_...` |\n\n### Plugin Configuration\n\n```typescript\ninterface PaystackConfig {\n  secret_key: string      // Paystack secret key\n  public_key: string      // Paystack public key  \n  webhook_secret?: string // Optional webhook secret\n}\n```\n\n## 🖥️ Frontend Integration\n\n### Next.js Storefront Integration\n\nInstall required dependencies in your storefront:\n\n```bash\nnpm install @paystack/inline-js\n```\n\n### 1. Paystack Payment Component\n\n```tsx\n// components/PaystackPayment.tsx\n\"use client\"\n\nimport { useState } from \"react\"\nimport { toast } from \"@medusajs/ui\"\n\ninterface PaystackPaymentProps {\n  session: any\n  cart: any\n  onPaymentCompleted: (reference: string) =\u003e void\n  onPaymentFailed: (error: string) =\u003e void\n}\n\nexport function PaystackPayment({ \n  session, \n  cart,\n  onPaymentCompleted, \n  onPaymentFailed \n}: PaystackPaymentProps) {\n  const [isLoading, setIsLoading] = useState(false)\n\n  const initializePayment = async () =\u003e {\n    try {\n      setIsLoading(true)\n      \n      const { access_code, authorization_url } = session.data\n      \n      if (!access_code) {\n        throw new Error(\"Payment session not ready\")\n      }\n\n      // Use Paystack Popup\n      const PaystackPop = (await import(\"@paystack/inline-js\")).default\n      const popup = new PaystackPop()\n      \n      popup.resumeTransaction(access_code, {\n        onClose: () =\u003e {\n          setIsLoading(false)\n          toast.warning(\"Payment was cancelled\")\n        },\n        onSuccess: (transaction: any) =\u003e {\n          setIsLoading(false)\n          toast.success(\"Payment successful!\")\n          onPaymentCompleted(transaction.reference)\n        },\n        onError: (error: any) =\u003e {\n          setIsLoading(false)\n          let errorMessage = \"Payment failed\"\n          \n          if (error.message?.toLowerCase().includes('not found')) {\n            errorMessage = \"Payment session expired. Please try again.\"\n          }\n          \n          toast.error(errorMessage)\n          onPaymentFailed(errorMessage)\n        }\n      })\n      \n    } catch (error) {\n      setIsLoading(false)\n      onPaymentFailed(error.message)\n    }\n  }\n\n  return (\n    \u003cbutton\n      onClick={initializePayment}\n      disabled={isLoading}\n      className=\"w-full bg-green-600 hover:bg-green-700 text-white font-medium py-3 px-4 rounded-lg\"\n    \u003e\n      {isLoading ? \"Processing...\" : \"Pay with Paystack\"}\n    \u003c/button\u003e\n  )\n}\n```\n\n### 2. Payment Session Hook\n\n```tsx\n// hooks/use-paystack-session.tsx\nimport { useState, useEffect } from \"react\"\n\nexport function usePaystackSession({ session, cart, onSessionUpdate }) {\n  const [isReady, setIsReady] = useState(false)\n  const [isUpdating, setIsUpdating] = useState(false)\n\n  useEffect(() =\u003e {\n    if (session?.data) {\n      const { authorization_url, access_code, session_expired, payment_completed } = session.data\n      \n      const isExpiredOrCompleted = session_expired === true || payment_completed === true\n      const ready = !isExpiredOrCompleted \u0026\u0026 \n                   ((session.status === \"requires_more\" \u0026\u0026 (authorization_url || access_code)) ||\n                    session.status === \"authorized\")\n      \n      setIsReady(ready)\n    }\n  }, [session])\n\n  const updateSession = async (customerData?: any) =\u003e {\n    if (!cart?.payment_collection?.id) return false\n\n    setIsUpdating(true)\n    \n    try {\n      const response = await fetch(\n        `${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/payment-collections/${cart.payment_collection.id}/payment-sessions`,\n        {\n          method: \"POST\",\n          headers: {\n            \"Content-Type\": \"application/json\",\n            \"x-publishable-api-key\": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,\n          },\n          body: JSON.stringify({\n            provider_id: \"pp_paystack_paystack\",\n            data: { email: customerData?.email || cart.email }\n          }),\n        }\n      )\n\n      if (response.ok) {\n        const result = await response.json()\n        const paystackSession = result.payment_collection.payment_sessions?.find(\n          (s: any) =\u003e s.provider_id === 'pp_paystack_paystack'\n        )\n        \n        if (paystackSession \u0026\u0026 onSessionUpdate) {\n          onSessionUpdate(paystackSession)\n          return true\n        }\n      }\n      \n      return false\n    } catch (error) {\n      console.error('Session update failed:', error)\n      return false\n    } finally {\n      setIsUpdating(false)\n    }\n  }\n\n  return {\n    isReady,\n    isUpdating,\n    updateSession,\n    sessionData: session?.data,\n  }\n}\n```\n\n### 3. Integration in Checkout\n\n```tsx\n// In your checkout component\nimport { PaystackPayment } from \"./PaystackPayment\"\nimport { usePaystackSession } from \"./use-paystack-session\"\n\nexport function CheckoutPayment({ cart }) {\n  const paystackSession = cart.payment_collection?.payment_sessions?.find(\n    (session) =\u003e session.provider_id === \"pp_paystack_paystack\"\n  )\n\n  const { isReady, updateSession } = usePaystackSession({\n    session: paystackSession,\n    cart,\n    onSessionUpdate: (updatedSession) =\u003e {\n      // Handle session update\n    }\n  })\n\n  const handlePaymentCompleted = async (reference: string) =\u003e {\n    // Complete the cart/order\n    const response = await fetch(`/store/carts/${cart.id}/complete`, {\n      method: \"POST\",\n      headers: {\n        \"x-publishable-api-key\": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,\n      },\n    })\n    \n    if (response.ok) {\n      // Redirect to success page\n      window.location.href = \"/order/confirmed\"\n    }\n  }\n\n  if (!paystackSession) {\n    return \u003cdiv\u003ePaystack payment not available\u003c/div\u003e\n  }\n\n  return (\n    \u003cPaystackPayment\n      session={paystackSession}\n      cart={cart}\n      onPaymentCompleted={handlePaymentCompleted}\n      onPaymentFailed={(error) =\u003e console.error(\"Payment failed:\", error)}\n    /\u003e\n  )\n}\n```\n\n## 🔧 Backend Features\n\n### Payment Session Lifecycle\n\nThe plugin correctly implements Medusa v2's payment session states:\n\n- **`pending`** - Session created but awaiting customer email\n- **`requires_more`** - Ready for payment (has authorization URL)\n- **`authorized`** - Payment completed successfully\n- **`error`** - Payment failed or session error\n\n### Session Refresh \u0026 Expiry Handling\n\nThe plugin automatically handles:\n\n- Expired access codes\n- Completed payment sessions\n- Session recreation for retry scenarios\n- Customer email validation\n\n### Webhook Support\n\nSet up webhooks in your Paystack dashboard:\n\n```txt\nWebhook URL: https://yourdomain.com/store/paystack/webhook/route\nEvents: charge.success, charge.failed\n```\n\n### API Endpoints\n\nThe plugin provides these endpoints:\n\n- `POST /store/paystack/webhook/route` - Webhook handler\n- `GET /admin/paystack/route` - Admin verification\n- `GET /store/plugin/route` - Plugin status\n\n## 💰 Supported Currencies\n\n| Currency | Code | Subunit | Example |\n|----------|------|---------|---------|\n| Nigerian Naira | NGN | kobo | ₦100.00 |\n| US Dollar | USD | cents | $1.00 |\n| Ghanaian Cedi | GHS | pesewas | GH₵1.00 |\n| South African Rand | ZAR | cents | R1.00 |\n\n## 🔍 Testing\n\n### Test Cards\n\nUse these test cards in development:\n\n```txt\n# Successful Payment\nCard: 4084 0840 8408 4081\nCVV: 408\nExpiry: 12/25\n\n# Declined Payment  \nCard: 4084 0840 8408 4096\nCVV: 408\nExpiry: 12/25\n```\n\n### Development Workflow\n\n1. Use test API keys from Paystack dashboard\n2. Test with different scenarios (success, failure, expired sessions)\n3. Verify webhook delivery in Paystack dashboard\n4. Test session refresh and retry mechanisms\n\n## 🛠️ Troubleshooting\n\n### Common Issues\n\n#### \"Not found\" Error in Payment Popup\n\n- This occurs when trying to reuse an expired/completed access code\n- The plugin automatically handles this with session refresh\n- Ensure your frontend implements the session refresh logic\n\n#### Session Status Undefined Error\n\n- Fixed in v1.3.3+ - plugin now always returns valid status\n- Update to latest version if experiencing this\n\n#### Customer Email Missing\n\n- Ensure customer email is provided during session creation\n- Plugin returns `pending` status until email is available\n\n#### Webhook Not Triggering\n\n- Verify webhook URL is publicly accessible\n- Check webhook secret configuration\n- Review Paystack dashboard for delivery logs\n\n### Debugging\n\nEnable detailed logging:\n\n```env\nLOG_LEVEL=debug\nNODE_ENV=development\n```\n\nThis will show detailed logs for:\n\n- Payment session creation\n- Paystack API responses  \n- Session status changes\n- Error details\n\n## 📊 Migration from v1\n\nIf upgrading from v1, note these breaking changes:\n\n1. **Provider ID**: Now uses `pp_paystack_paystack` format\n2. **Session Management**: New lifecycle handling\n3. **Configuration**: Updated to Medusa v2 format\n4. **Dependencies**: Uses official Paystack SDK\n\n### Migration Steps\n\n1. Update configuration to new format\n2. Install new version: `npm install @alexasomba/medusa-paystack-plugin-v2`\n3. Update frontend integration to use new session states\n4. Test thoroughly with new session refresh logic\n\n## 🚀 Version History\n\n- **v1.3.3** - Fixed session status handling and expiry management\n- **v1.3.0** - Added session refresh and error handling improvements  \n- **v1.2.0** - Multi-currency support and official Paystack SDK\n- **v1.1.0** - Webhook support and admin integration\n- **v1.0.0** - Initial release for Medusa v2\n\n## 📄 License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## 📞 Support\n\n- 📧 **Email**: \u003calex@asomba.com\u003e\n- 💬 **GitHub Issues**: [Report Issues](https://github.com/alexasomba/medusa-paystack-plugin-v2/issues)\n- 📖 **Documentation**: [Medusa Docs](https://docs.medusajs.com)\n- 🌍 **Community**: [Medusa Discord](https://discord.gg/medusajs)\n\n## ⭐ Show Your Support\n\nIf this plugin helps your project, please give it a star on GitHub! ⭐\n\n---\n\nBuilt with ❤️ for the Medusa community\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexasomba%2Fmedusa-v2-paystack-payment-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexasomba%2Fmedusa-v2-paystack-payment-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexasomba%2Fmedusa-v2-paystack-payment-plugin/lists"}