Upgraded to medusajs 2.5.0 and the razorpay payment module is tested now. It is accepting basic payment. Some functions might still not be working.

This commit is contained in:
2025-02-17 02:19:45 +00:00
parent 6e3216a0ff
commit 7909d97cf8
3 changed files with 3056 additions and 3118 deletions

View File

@ -22,29 +22,29 @@
"test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit" "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit"
}, },
"dependencies": { "dependencies": {
"@medusajs/admin-sdk": "^2.4.0", "@medusajs/admin-sdk": "^2.5.0",
"@medusajs/cache-redis": "^2.4.0", "@medusajs/cache-redis": "^2.5.0",
"@medusajs/cli": "^2.4.0", "@medusajs/cli": "^2.5.0",
"@medusajs/event-bus-redis": "^2.4.0", "@medusajs/event-bus-redis": "^2.5.0",
"@medusajs/file-s3": "^2.4.0", "@medusajs/file-s3": "^2.5.0",
"@medusajs/framework": "^2.4.0", "@medusajs/framework": "^2.5.0",
"@medusajs/medusa": "^2.4.0", "@medusajs/medusa": "^2.5.0",
"@medusajs/workflow-engine-redis": "^2.4.0", "@medusajs/workflow-engine-redis": "^2.5.0",
"@mikro-orm/core": "^6.4.3", "@mikro-orm/core": "6.4.3",
"@mikro-orm/knex": "^6.4.3", "@mikro-orm/knex": "6.4.3",
"@mikro-orm/migrations": "^6.4.3", "@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "^6.4.3", "@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1", "awilix": "^8.0.1",
"pg": "^8.13.0", "pg": "^8.13.0",
"razorpay": "^2.9.5" "razorpay": "^2.9.5"
}, },
"devDependencies": { "devDependencies": {
"@medusajs/test-utils": "^2.4.0", "@medusajs/test-utils": "^2.5.0",
"@mikro-orm/cli": "^6.4.3", "@mikro-orm/cli": "6.4.3",
"@swc/core": "1.5.7", "@swc/core": "1.5.7",
"@swc/jest": "^0.2.36", "@swc/jest": "^0.2.36",
"@types/jest": "^29.5.13", "@types/jest": "^29.5.13",
"@types/node": "^20.0.0", "@types/node": "^22.13.4",
"@types/react": "^18.3.2", "@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.2.25",
"jest": "^29.7.0", "jest": "^29.7.0",
@ -54,9 +54,10 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"vite": "^5.2.11" "vite": "^5.2.11",
"yalc": "^1.0.0-pre.53"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=22"
} }
} }

View File

@ -1,29 +1,39 @@
import Razorpay from 'razorpay';
// Adjust these imports to match your actual Medusa versions/paths
import { AbstractPaymentProvider, BigNumber, MedusaError, PaymentSessionStatus } from '@medusajs/framework/utils';
import { import {
CreatePaymentProviderSession, AuthorizePaymentInput,
AuthorizePaymentOutput,
CancelPaymentInput,
CancelPaymentOutput,
CapturePaymentInput,
CapturePaymentOutput,
DeletePaymentInput,
DeletePaymentOutput,
GetPaymentStatusInput,
GetPaymentStatusOutput,
InitiatePaymentInput,
InitiatePaymentOutput,
Logger, Logger,
ProviderWebhookPayload, ProviderWebhookPayload,
RefundPaymentInput,
RefundPaymentOutput,
RetrievePaymentInput,
RetrievePaymentOutput,
UpdatePaymentInput,
UpdatePaymentOutput,
WebhookActionResult, WebhookActionResult,
} from '@medusajs/framework/types'; } from '@medusajs/framework/types';
import { AbstractPaymentProvider, BigNumber, MedusaError, PaymentSessionStatus } from '@medusajs/framework/utils';
import Razorpay from 'razorpay';
// Adjust these imports to match your actual Medusa versions/paths
import { interface RazorpayProviderOptions {
UpdatePaymentProviderSession,
PaymentProviderError,
PaymentProviderSessionResponse,
} from '@medusajs/framework/types';
type RazorpayProviderOptions = {
key_id: string; key_id: string;
key_secret: string; key_secret: string;
// Add other config options if needed // Add other config options if needed
}; }
type InjectedDependencies = { interface InjectedDependencies {
logger: Logger; logger: Logger;
}; }
export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayProviderOptions> { export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayProviderOptions> {
protected logger_: Logger; protected logger_: Logger;
@ -54,51 +64,31 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
} }
} }
/** async initiatePayment(data: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
* Initiates a payment session (e.g. create an order in Razorpay). // Create an order in Razorpay
* Return a PaymentProviderSessionResponse: const { currency_code, amount } = data;
* { session_data: Record<string, unknown> }
*/
async initiatePayment(
context: CreatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Example: create an order in Razorpay
const { currency_code, context: customerDetails } = context;
try { try {
const amount = (context.amount as number) * 100;
// const currency = (data.currency as string) || "INR"
const order = await this.razorpay.orders.create({ const order = await this.razorpay.orders.create({
amount, amount: 100 * (amount as number),
currency: currency_code.toUpperCase(), currency: currency_code.toUpperCase(),
receipt: `receipt_${Date.now()}`, receipt: `receipt_${Date.now()}`,
payment_capture: true, // auto-capture payment_capture: true, // auto-capture
}); });
// Return a PaymentProviderSessionResponse
return { return {
...order, id: order.id,
data: { data: {
id: order.id, ...order,
}, },
}; };
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return { throw e;
error: e,
code: 'unknown',
detail: e,
};
} }
} }
/** async retrievePayment(data: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
* Retrieves the payment session data from Razorpay or from your local records. const externalId = data.data.id;
* Must return either session_data or throw a PaymentProviderError.
*/
async retrievePayment(
paymentSessionData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
try { try {
const razorpayOrderId = externalId as string; const razorpayOrderId = externalId as string;
if (!razorpayOrderId) { if (!razorpayOrderId) {
@ -108,158 +98,95 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
// Example: fetch the order from Razorpay // Example: fetch the order from Razorpay
const order = await this.razorpay.orders.fetch(razorpayOrderId); const order = await this.razorpay.orders.fetch(razorpayOrderId);
return { return { data: { ...order, status: order.status || 'created', id: externalId } };
...order,
status: order.status || 'created',
id: externalId,
};
} catch (e) { } catch (e) {
return { throw e;
error: e,
code: 'unknown',
detail: e,
};
} }
} }
/** async updatePayment(data: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
* Updates the payment session. The signature typically expects: const { amount, currency_code, data: ppiData } = data;
* (context: UpdatePaymentProviderSession) => Promise<PaymentProviderError | PaymentProviderSessionResponse> const externalId = ppiData.id;
*/
async updatePayment(
context: UpdatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Deconstruct the data from context
const { amount, currency_code, context: customerDetails, data } = context;
const externalId = data.id;
try { try {
const response = await this.razorpay.payments.edit(externalId as string, { const response = await this.razorpay.payments.edit(externalId as string, {
notes: { notes: {
amount: amount as number, amount: amount as number,
currency: currency_code, currency: currency_code.toUpperCase(),
}, },
}); });
return { return {
...response, data: { ...response },
data: {
id: response.id,
},
}; };
} catch (e) { } catch (e) {
return { throw e;
error: e,
code: 'unknown',
detail: e,
};
} }
} }
/** async authorizePayment(data: AuthorizePaymentInput): Promise<AuthorizePaymentOutput> {
* Authorize the payment (e.g., verify webhook signature, check that payment is successful). const externalId = data.data.id;
* Return updated session data or PaymentProviderError.
*/
async authorizePayment(
paymentSessionData: Record<string, unknown>,
context: Record<string, unknown>,
): Promise<
| PaymentProviderError
| {
status: PaymentSessionStatus;
data: PaymentProviderSessionResponse['data'];
}
> {
const externalId = paymentSessionData.id;
try { try {
console.log('authorizePayment externalId', externalId);
// Here you could verify a signature or confirm the payment status in Razorpay // Here you could verify a signature or confirm the payment status in Razorpay
const paymentData = await this.razorpay.orders.fetchPayments(externalId as string); const paymentData = await this.razorpay.orders.fetchPayments(externalId as string);
// if the externalId is paymentId, then use this.razorpay.payments.fetch(externalId as string) // if the externalId is paymentId, then use this.razorpay.payments.fetch(externalId as string)
// const paymentData = await this.razorpay.payments.fetch(externalId as string) // const paymentData = await this.razorpay.payments.fetch(externalId as string)
return { return {
data: { data: {
...paymentSessionData, rporder: data.data,
id: externalId, rpauth: paymentData,
id: paymentData.items[0].id,
status: this.rpToMedusaStatus(paymentData.items[0].status),
}, },
status: PaymentSessionStatus.AUTHORIZED, status: this.rpToMedusaStatus(paymentData.items[0].status),
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
}; };
} catch (e) {
throw e;
} }
} }
/** /**
* Capture the payment if not auto-captured. * Capture the payment if not auto-captured.
*/ */
async capturePayment( async capturePayment(data: CapturePaymentInput): Promise<CapturePaymentOutput> {
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
try { try {
const externalId = paymentData.id; if (data.data.status === 'captured') {
const { amount } = paymentData; return data;
}
const externalId = data.data.id;
const amount = (data.data.rporder as { amount: number }).amount;
console.log('capturePayment', data, externalId, amount);
// e.g. this.razorpay.payments.capture(paymentId, amount) if not auto-captured // e.g. this.razorpay.payments.capture(paymentId, amount) if not auto-captured
const newData = await this.razorpay.payments.capture(externalId as string, amount as number, 'INR'); const newData = await this.razorpay.payments.capture(externalId as string, amount as number, 'INR');
return { return { data: { ...newData, id: externalId } };
...newData,
// status: "captured",
id: externalId,
};
} catch (e) { } catch (e) {
return { console.log('capturePayment error', e);
error: e, throw e;
code: 'unknown',
detail: e,
};
} }
} }
/** /**
* Refund the payment in Razorpay. * Refund the payment in Razorpay.
*/ */
async refundPayment( async refundPayment(data: RefundPaymentInput): Promise<RefundPaymentOutput> {
paymentData: Record<string, unknown>, const externalId = data.data.id;
refundAmount: number,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentData.id;
try { try {
const newData = await this.razorpay.payments.refund(externalId as string, { amount: refundAmount }); const newData = await this.razorpay.payments.refund(externalId as string, { amount: data.amount as number });
return { return { data: { ...newData, id: externalId } };
...newData,
id: externalId,
// refunded_amount: refundAmount,
// status: "refunded",
};
} catch (e) { } catch (e) {
return { throw e;
error: e,
code: 'unknown',
detail: e,
};
} }
} }
/** /**
* Cancel a payment if it hasn't been paid. For Razorpay, you may just mark the order as canceled in your system. * Cancel a payment if it hasn't been paid. For Razorpay, you may just mark the order as canceled in your system.
*/ */
async cancelPayment( async cancelPayment(data: CancelPaymentInput): Promise<CancelPaymentOutput> {
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
try { try {
const externalId = paymentData.id; const externalId = data.data.id;
return { return { data: { id: externalId, status: 'canceled' } };
id: externalId, } catch (e) {
status: 'canceled', throw e;
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
};
} }
} }
@ -269,23 +196,16 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
* (paymentSessionData: Record<string, unknown>) => Promise<Record<string, unknown> | PaymentProviderError> * (paymentSessionData: Record<string, unknown>) => Promise<Record<string, unknown> | PaymentProviderError>
* or might require returning PaymentProviderSessionResponse. Adjust if needed. * or might require returning PaymentProviderSessionResponse. Adjust if needed.
*/ */
async deletePayment( async deletePayment(data: DeletePaymentInput): Promise<DeletePaymentOutput> {
paymentSessionData: Record<string, unknown>, const externalId = data.data.id;
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
try { try {
// If Razorpay does not allow deleting an order, just mark locally as deleted // If Razorpay does not allow deleting an order, just mark locally as deleted
// Return the final state or an empty object // Return the final state or an empty object
return { return {
id: externalId, data: { id: externalId, deleted: true },
deleted: true,
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
}; };
} catch (e) {
throw e;
} }
} }
@ -293,11 +213,15 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
* Get the status (PaymentSessionStatus) for the payment. * Get the status (PaymentSessionStatus) for the payment.
* Must return a PaymentSessionStatus enum value (e.g., "AUTHORIZED", "PENDING", "REQUIRES_MORE", "ERROR", etc.) * Must return a PaymentSessionStatus enum value (e.g., "AUTHORIZED", "PENDING", "REQUIRES_MORE", "ERROR", etc.)
*/ */
async getPaymentStatus(paymentSessionData: Record<string, unknown>): Promise<PaymentSessionStatus> { async getPaymentStatus(data: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
// For a minimal example: // For a minimal example:
const payment = await this.razorpay.payments.fetch(paymentSessionData.id as string); const payment = await this.razorpay.payments.fetch(data.data.id as string);
const status = payment.status; const status = payment.status;
switch (status?.toLowerCase()) { return { status: this.rpToMedusaStatus(status) };
}
rpToMedusaStatus(rpStatus: string): PaymentSessionStatus {
switch (rpStatus.toLowerCase()) {
case 'created': case 'created':
return PaymentSessionStatus.PENDING; return PaymentSessionStatus.PENDING;
case 'authorized': case 'authorized':
@ -305,7 +229,7 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
case 'captured': case 'captured':
return PaymentSessionStatus.CAPTURED; return PaymentSessionStatus.CAPTURED;
case 'refunded': case 'refunded':
return PaymentSessionStatus.CANCELED; // Payment was authorized before refund return PaymentSessionStatus.CANCELED;
case 'failed': case 'failed':
return PaymentSessionStatus.ERROR; return PaymentSessionStatus.ERROR;
default: default:

5893
yarn.lock

File diff suppressed because it is too large Load Diff