{"_id":"@alchemilla/medusa-razorpay","name":"@alchemilla/medusa-razorpay","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alchemilla/medusa-razorpay","version":"0.1.0","description":"Razorpay payment provider for Medusa v2. Supports INR, USD, and 100+ currencies with webhook handling.","author":"","license":"MIT","repository":{"type":"git","url":""},"exports":{"./package.json":"./package.json","./providers/*":"./.medusa/server/src/providers/*/index.js","./*":"./.medusa/server/src/*.js"},"keywords":["medusa","medusa-v2","medusa-plugin","medusa-provider","medusa-payment","razorpay","payment","payment-gateway","india"],"dependencies":{"razorpay":"^2.9.6"},"peerDependencies":{"@medusajs/framework":"^2.0.0","@medusajs/medusa":"^2.0.0"},"devDependencies":{"@medusajs/admin-sdk":"^2.0.0","@medusajs/admin-shared":"^2.0.0","@medusajs/cli":"^2.0.0","@medusajs/framework":"^2.0.0","@medusajs/medusa":"^2.0.0","@medusajs/ui":"^4.0.0","@types/node":"^20.0.0","@types/react":"^18.3.2","@types/react-dom":"^18.2.25","react":"^18.3.1","react-dom":"^18.3.1","ts-node":"^10.9.2","typescript":"^5.6.2","vite":"^5.4.14"},"engines":{"node":">=20"},"scripts":{"build":"medusa plugin:build","dev":"medusa plugin:develop","typecheck":"tsc --noEmit"},"_id":"@alchemilla/medusa-razorpay@0.1.0","_integrity":"sha512-7XdgYZj3dUrFigysyjO/3jLBGSshmgYKN04AFF/w686v8TcH57nc5DJJGp+Aac6dltdyENSjCP9dBdwmsVLZVg==","_resolved":"/tmp/fa02ec221b9ee23f176ccb36c031dd3a/alchemilla-medusa-razorpay-0.1.0.tgz","_from":"file:alchemilla-medusa-razorpay-0.1.0.tgz","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-7XdgYZj3dUrFigysyjO/3jLBGSshmgYKN04AFF/w686v8TcH57nc5DJJGp+Aac6dltdyENSjCP9dBdwmsVLZVg==","shasum":"6b63c4f6c206872c4d610a14e533c16fc1a85d04","tarball":"https://registry.npmjs.org/@alchemilla/medusa-razorpay/-/medusa-razorpay-0.1.0.tgz","fileCount":12,"unpackedSize":64472,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD6dFalysYFw9+NrZH7jceYEXQido7wsnYvpd3Ivlt/AAIhALNyCcM/cSnz7qPB5ttzKwAE53uK0fbPJdei0vxizaBL"}]},"_npmUser":{"name":"alchemillahq","email":"hayzam@alchemilla.io"},"directories":{},"maintainers":[{"name":"alchemillahq","email":"hayzam@alchemilla.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/medusa-razorpay_0.1.0_1779884365969_0.48436962922574045"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-27T12:19:25.744Z","0.1.0":"2026-05-27T12:19:26.131Z","modified":"2026-05-27T12:19:26.390Z"},"maintainers":[{"name":"alchemillahq","email":"hayzam@alchemilla.io"}],"description":"Razorpay payment provider for Medusa v2. Supports INR, USD, and 100+ currencies with webhook handling.","keywords":["medusa","medusa-v2","medusa-plugin","medusa-provider","medusa-payment","razorpay","payment","payment-gateway","india"],"repository":{"type":"git","url":""},"license":"MIT","readme":"# @alchemilla/medusa-razorpay\n\nRazorpay payment provider for [Medusa v2](https://medusajs.com). A standalone module that enables Razorpay payments with webhook handling, proper currency conversion, customer management, and admin dashboard widgets.\n\n## Installation\n\n```bash\nnpm install @alchemilla/medusa-razorpay\n# or\nyarn add @alchemilla/medusa-razorpay\n# or\npnpm add @alchemilla/medusa-razorpay\n```\n\n## Backend Configuration\n\n### 1. Environment Variables\n\nAdd to your Medusa backend `.env` file:\n\n```env\nRAZORPAY_ID=rzp_test_xxx               # Razorpay API key ID\nRAZORPAY_SECRET=yyy                     # Razorpay API key secret\nRAZORPAY_ACCOUNT=acc_xxx                # Razorpay account/merchant ID\nRAZORPAY_WEBHOOK_SECRET=whsec_zzz       # From Razorpay dashboard > Settings > Webhooks\n```\n\n### 2. medusa-config.ts\n\nRegister the provider in your Medusa config:\n\n```ts\nimport { defineConfig } from \"@medusajs/framework/utils\"\n\nmodule.exports = defineConfig({\n  // ...\n  modules: [\n    // ... other modules\n    {\n      resolve: \"@medusajs/medusa/payment\",\n      options: {\n        providers: [\n          {\n            resolve: \"@alchemilla/medusa-razorpay/providers/payment-razorpay/src\",\n            id: \"razorpay\",\n            options: {\n              key_id: process.env.RAZORPAY_ID || \"\",\n              key_secret: process.env.RAZORPAY_SECRET || \"\",\n              razorpay_account: process.env.RAZORPAY_ACCOUNT || \"\",\n              webhook_secret: process.env.RAZORPAY_WEBHOOK_SECRET || \"\",\n              manual_expiry_period: 20,        // minutes (required if auto_capture is off)\n              refund_speed: \"normal\",           // \"normal\" | \"optimum\"\n              auto_capture: false,              // Set true for automatic payment capture\n            },\n          },\n        ],\n      },\n    },\n  ],\n})\n```\n\n> **Important notes on options:**\n> - `refund_speed` is used at refund time only — it is **not** sent in the order creation body (Razorpay rejects it there).\n> - Either `manual_expiry_period` or `automatic_expiry_period` is required.\n> - `auto_capture: true` requires `automatic_expiry_period` instead of `manual_expiry_period`.\n\n### 3. Webhook Setup\n\nIn your Razorpay dashboard, create a webhook with the URL:\n\n```\nhttps://your-domain.com/hooks/payment/razorpay_razorpay\n```\n\nSelect these events:\n- `payment.authorized`\n- `payment.captured`\n- `payment.failed`\n\n## Storefront Integration\n\n### 1. Install `react-razorpay`\n\n```bash\nnpm install react-razorpay\n```\n\n### 2. Environment Variables\n\nAdd to your storefront `.env.local`:\n\n```env\nNEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_test_xxx    # Same as RAZORPAY_ID / key_id above\nNEXT_PUBLIC_SHOP_NAME=Your Store Name\nNEXT_PUBLIC_MEDUSA_BACKEND_URL=https://your-backend.com   # Must be publicly reachable for callbacks\n```\n\n> `NEXT_PUBLIC_MEDUSA_BACKEND_URL` must be a publicly reachable URL (or tunneled via ngrok/localtunnel) so Razorpay can POST the payment response to the callback endpoint.\n\n### 3. Backend: Storefront URL\n\nAdd to your backend `.env` so the callback redirect knows where to send the user:\n\n```env\nSTOREFRONT_URL=http://localhost:8000\n```\n\n### 4. Constants\n\nIn `src/lib/constants.tsx`, add:\n\n```tsx\n// Add to paymentInfoMap\npp_razorpay_razorpay: {\n  title: \"Razorpay\",\n  icon: <CreditCard />,\n},\n\n// Add the helper\nexport const isRazorpay = (providerId?: string) => {\n  return providerId?.startsWith(\"pp_razorpay\")\n}\n```\n\n### 5. Razorpay Payment Button\n\nCreate `src/modules/checkout/components/payment-button/razorpay-payment-button.tsx`:\n\n```tsx\n\"use client\"\n\nimport { placeOrder } from \"@lib/data/cart\"\nimport { HttpTypes } from \"@medusajs/types\"\nimport { Button } from \"@modules/common/components/ui\"\nimport { useRazorpay, RazorpayOrderOptions } from \"react-razorpay\"\nimport React, { useCallback, useState } from \"react\"\nimport ErrorMessage from \"../error-message\"\n\ntype RazorpayPaymentButtonProps = {\n  cart: HttpTypes.StoreCart\n  notReady: boolean\n  \"data-testid\"?: string\n}\n\nconst RazorpayPaymentButton: React.FC<RazorpayPaymentButtonProps> = ({\n  cart,\n  notReady,\n  \"data-testid\": dataTestId,\n}) => {\n  const [submitting, setSubmitting] = useState(false)\n  const [errorMessage, setErrorMessage] = useState<string | null>(null)\n  const { error, isLoading, Razorpay } = useRazorpay()\n\n  const session = cart.payment_collection?.payment_sessions?.find(\n    (s) => s.status === \"pending\" || s.status === \"requires_more\"\n  )\n\n  const razorpayOrder = session?.data?.razorpayOrder as Record<string, any> | undefined\n\n  const handlePayment = useCallback(async () => {\n    if (!razorpayOrder?.id || !session) return\n    setSubmitting(true)\n\n    // Place the order first, then open payment modal.\n    // On payment success, Razorpay redirects to the callback URL\n    // which verifies the signature and redirects back to the storefront.\n    try {\n      await placeOrder()\n    } catch (err) {\n      setErrorMessage((err as Error).message)\n      setSubmitting(false)\n      return\n    }\n\n    const options: RazorpayOrderOptions = {\n      key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID || \"\",\n      amount: Math.round(session.amount * 100),\n      currency: (cart.currency_code?.toUpperCase() || \"EUR\") as any,\n      name: process.env.NEXT_PUBLIC_SHOP_NAME || \"Your Store\",\n      description: `Order ${razorpayOrder.id}`,\n      order_id: razorpayOrder.id,\n      callback_url: `${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/razorpay/callback`,\n      redirect: true,\n      prefill: {\n        name: `${cart.billing_address?.first_name ?? \"\"} ${cart.billing_address?.last_name ?? \"\"}`.trim() || cart.email || \"\",\n        email: cart.email ?? \"\",\n        contact: cart.shipping_address?.phone ?? cart.billing_address?.phone ?? undefined,\n        method: (cart.currency_code === \"inr\") ? \"upi\" as any : undefined,\n      },\n      modal: {\n        ondismiss: () => { setSubmitting(false); setErrorMessage(\"Payment cancelled\") },\n        escape: true,\n        animation: true,\n      },\n    }\n\n    const rzp = new Razorpay(options)\n    rzp.on(\"payment.failed\", (response: any) => {\n      setErrorMessage(response.error?.description || \"Payment failed\")\n      setSubmitting(false)\n    })\n    rzp.open()\n  }, [Razorpay, razorpayOrder, session, cart])\n\n  if (isLoading) return <Button disabled isLoading>Loading...</Button>\n  if (error) return <Button disabled>Razorpay unavailable</Button>\n\n  return (\n    <>\n      <Button\n        disabled={notReady || submitting || !razorpayOrder?.id}\n        onClick={handlePayment}\n        size=\"large\"\n        isLoading={submitting}\n        data-testid={dataTestId}\n      >\n        Pay with Razorpay\n      </Button>\n      <ErrorMessage error={errorMessage} data-testid=\"razorpay-payment-error-message\" />\n    </>\n  )\n}\n\nexport default RazorpayPaymentButton\n```\n\n### 6. Wire into PaymentButton\n\nIn `src/modules/checkout/components/payment-button/index.tsx`:\n\n```tsx\nimport { isManual, isRazorpay, isStripeLike } from \"@lib/constants\"\nimport RazorpayPaymentButton from \"./razorpay-payment-button\"\n\n// Inside the PaymentButton component's switch:\ncase isRazorpay(paymentSession?.provider_id):\n  return (\n    <RazorpayPaymentButton\n      notReady={notReady}\n      cart={cart}\n      data-testid={dataTestId}\n    />\n  )\n```\n\n## API Routes (included)\n\nThe package includes a callback endpoint for Razorpay payment verification:\n\n```\nPOST /razorpay/callback\n```\n\nWhen the customer completes payment in the Razorpay modal, Razorpay redirects the browser to this endpoint with the payment signature. The endpoint:\n\n1. Receives `razorpay_payment_id`, `razorpay_order_id`, `razorpay_signature`\n2. Verifies the signature using HMAC-SHA256 with your `key_secret`\n3. Redirects the browser back to the storefront order confirmation page\n\n## Checkout Flow\n\n1. Customer adds items to cart\n2. At checkout, selects **Razorpay** as payment method\n3. On \"Continue to review\", a Razorpay order is created via backend\n4. On \"Pay with Razorpay\", `placeOrder()` is called first to finalize the order\n5. The Razorpay modal opens for payment\n6. Customer completes payment in the Razorpay modal\n7. Razorpay redirects to `POST /razorpay/callback` on your backend\n8. Backend verifies the payment signature and redirects to the order confirmation page\n9. Razorpay also sends a webhook to `/hooks/payment/razorpay_razorpay` to update payment status\n\n> **Why `callback_url` + webhooks?** Two mechanisms for reliability: the callback confirms payment to the customer immediately via browser redirect; webhooks update payment status server-to-server even if the redirect fails.\n>\n> **Why `placeOrder()` before the modal?** Creates the order first so it exists regardless of what happens in the Razorpay modal. The webhook can then update its payment status independently.\n\n### Local Development\n\nFor webhooks and callbacks to reach your local backend, expose it with a tunnel:\n\n```bash\n# Using localtunnel\nnpx localtunnel --port 9000 --subdomain your-subdomain\n\n# Using ngrok\nngrok http 9000\n```\n\nThen set `NEXT_PUBLIC_MEDUSA_BACKEND_URL` in your storefront to the tunnel URL and configure the webhook URL in Razorpay dashboard accordingly.\n\n### License\n\nBuilt upon inspiration from the [Payment-Razorpay](https://medusajs.com/integrations/@sgftechpayment-razorpay/) provider by [SGFGOV](https://github.com/SGFGOV/medusa-payment-plugins).","readmeFilename":"README.md","_rev":"1-9b8a5a073562644a51a3c32555bd4542"}