P
Payu payment plugin
PayU India payment gateway plugin for MedusaJS 2.x with redirect-based checkout, webhook support, hash verification, and refunds.
PayU Payment Plugin for MedusaJS 2
PayU India payment gateway plugin for MedusaJS 2.x with redirect-based checkout flow.
Features
- ✅ Redirect-based checkout - Seamless PayU hosted checkout integration
- ✅ Webhook support - Automatic payment status updates via PayU webhooks
- ✅ Refund support - Full and partial refunds through PayU API
- ✅ Hash verification - Secure SHA-512 transaction validation
- ✅ TypeScript - Full type safety with comprehensive type definitions
- ✅ Payment verification workflow - Built-in workflow for custom payment verification
Installation
npm install medusa-payu-payment-plugin# oryarn add medusa-payu-payment-plugin
Configuration
1. Environment Variables
Add to your file:
# PayU CredentialsPAYU_MERCHANT_KEY=your_merchant_keyPAYU_MERCHANT_SALT=your_merchant_saltPAYU_ENVIRONMENT=test # or "production"# Your storefront URL (for redirect callbacks)STOREFRONT_URL=http://localhost:8000
2. MedusaJS Config
Add to your :
import { defineConfig } from "@medusajs/framework/utils"export default defineConfig({// ... other configmodules: [{resolve: "@medusajs/medusa/payment",options: {providers: [{resolve: "medusa-payu-payment-plugin/providers/payu",id: "payu",options: {merchantKey: process.env.PAYU_MERCHANT_KEY,merchantSalt: process.env.PAYU_MERCHANT_SALT,environment: process.env.PAYU_ENVIRONMENT || "test",},},],},},],})
3. Enable for Region
In Medusa Admin:
- Go to Settings → Regions
- Select your region
- Add as a payment provider
Frontend Integration
Payment Flow Overview
- Customer selects PayU at checkout
- Frontend retrieves payment session from cart
- Frontend creates a form and redirects to PayU
- Customer completes payment on PayU's hosted page
- PayU redirects back to your storefront
- Webhook updates order status automatically
React/Next.js Example
"use client"function PayUPaymentButton({ cart }) {const handlePayment = async () => {// Get PayU payment sessionconst paymentSession = cart.payment_collection?.payment_sessions?.find((session) => session.provider_id === "pp_payu_payu")if (!paymentSession?.data?.form_data) {console.error("PayU session not found")return}const { form_data, paymentUrl } = paymentSession.data// Create and submit hidden formconst form = document.createElement("form")form.method = "POST"form.action = paymentUrlObject.entries(form_data).forEach(([key, value]) => {const input = document.createElement("input")input.type = "hidden"input.name = keyinput.value = String(value)form.appendChild(input)})document.body.appendChild(form)form.submit()}return (<buttononClick={handlePayment}className="btn-primary">Pay with PayU</button>)}
Payment Session Structure
The payment session data contains:
{txnid: string // Unique transaction IDamount: string // Amount with 2 decimals (e.g., "999.00")productinfo: string // Product/order descriptionfirstname: string // Customer first nameemail: string // Customer emailphone: string // Customer phonehash: string // Security hash (SHA-512)paymentUrl: string // PayU checkout URLstatus: string // Payment statusform_data: { // Ready-to-submit form datakey: string // Merchant keytxnid: stringamount: stringproductinfo: stringfirstname: stringemail: stringphone: stringsurl: string // Success redirect URLfurl: string // Failure redirect URLhash: stringservice_provider: string}}
Webhook Setup
Configure the webhook URL in your PayU dashboard:
The webhook handler automatically:
- Verifies the response hash for security
- Updates payment status (authorized/failed)
- Triggers order completion workflow
API Reference
Provider ID
Supported Methods
| Method | Description |
|---|---|
| Creates payment session with hash and form data | |
| Verifies payment status with PayU API | |
| Marks payment as captured (auto-capture enabled) | |
| Initiates full or partial refund | |
| Cancels pending payment | |
| Handles PayU webhook callbacks |
Exported Workflow
You can use the verify payment workflow in your custom code:
import { verifyPayuPaymentWorkflow } from "medusa-payu-payment-plugin/workflows"// In your API route or subscriberconst { result } = await verifyPayuPaymentWorkflow(container).run({input: {txnid: "TXN_1234567890_abcd",},})if (result.success) {console.log("Payment status:", result.status)console.log("Transaction details:", result.transaction)}
Environment Variables
| Variable | Description | Required |
|---|---|---|
| PayU Merchant Key | Yes | |
| PayU Merchant Salt (Salt V1) | Yes | |
| or | No (default: ) | |
| Your storefront URL for redirects | Yes |
Testing
Use PayU test credentials in your test environment:
- Test URL: https://test.payu.in
- Test Cards: PayU Test Cards Documentation
Common Test Card Numbers
| Card Type | Number | CVV | Expiry |
|---|---|---|---|
| Visa | 4012001038443335 | 123 | Any future date |
| Mastercard | 5123456789012346 | 123 | Any future date |
Troubleshooting
Hash Mismatch Error
Ensure:
- You're using the correct Salt version (this plugin uses Salt V1)
- Amount has exactly 2 decimal places (e.g., )
- All mandatory fields match exactly between hash generation and form submission
Webhook Not Received
- Verify webhook URL is correct in PayU dashboard
- Ensure your server is publicly accessible
- Check server logs for incoming webhook requests
- Verify SSL certificate is valid (required for production)
Payment Session Not Found
Ensure:
- PayU is enabled as a payment provider for the region
- Payment collection is initialized before accessing session
- Provider ID is (includes the prefix)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch ()
- Commit your changes ()
- Push to the branch ()
- Open a Pull Request
License
MIT © SAM-AEL
See LICENSE for more information.