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"
},
"dependencies": {
"@medusajs/admin-sdk": "^2.4.0",
"@medusajs/cache-redis": "^2.4.0",
"@medusajs/cli": "^2.4.0",
"@medusajs/event-bus-redis": "^2.4.0",
"@medusajs/file-s3": "^2.4.0",
"@medusajs/framework": "^2.4.0",
"@medusajs/medusa": "^2.4.0",
"@medusajs/workflow-engine-redis": "^2.4.0",
"@mikro-orm/core": "^6.4.3",
"@mikro-orm/knex": "^6.4.3",
"@mikro-orm/migrations": "^6.4.3",
"@mikro-orm/postgresql": "^6.4.3",
"@medusajs/admin-sdk": "^2.5.0",
"@medusajs/cache-redis": "^2.5.0",
"@medusajs/cli": "^2.5.0",
"@medusajs/event-bus-redis": "^2.5.0",
"@medusajs/file-s3": "^2.5.0",
"@medusajs/framework": "^2.5.0",
"@medusajs/medusa": "^2.5.0",
"@medusajs/workflow-engine-redis": "^2.5.0",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/knex": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1",
"pg": "^8.13.0",
"razorpay": "^2.9.5"
},
"devDependencies": {
"@medusajs/test-utils": "^2.4.0",
"@mikro-orm/cli": "^6.4.3",
"@medusajs/test-utils": "^2.5.0",
"@mikro-orm/cli": "6.4.3",
"@swc/core": "1.5.7",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.13",
"@types/node": "^20.0.0",
"@types/node": "^22.13.4",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"jest": "^29.7.0",
@ -54,9 +54,10 @@
"react-dom": "^18.2.0",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"vite": "^5.2.11"
"vite": "^5.2.11",
"yalc": "^1.0.0-pre.53"
},
"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 {
CreatePaymentProviderSession,
AuthorizePaymentInput,
AuthorizePaymentOutput,
CancelPaymentInput,
CancelPaymentOutput,
CapturePaymentInput,
CapturePaymentOutput,
DeletePaymentInput,
DeletePaymentOutput,
GetPaymentStatusInput,
GetPaymentStatusOutput,
InitiatePaymentInput,
InitiatePaymentOutput,
Logger,
ProviderWebhookPayload,
RefundPaymentInput,
RefundPaymentOutput,
RetrievePaymentInput,
RetrievePaymentOutput,
UpdatePaymentInput,
UpdatePaymentOutput,
WebhookActionResult,
} 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 {
UpdatePaymentProviderSession,
PaymentProviderError,
PaymentProviderSessionResponse,
} from '@medusajs/framework/types';
type RazorpayProviderOptions = {
interface RazorpayProviderOptions {
key_id: string;
key_secret: string;
// Add other config options if needed
};
}
type InjectedDependencies = {
interface InjectedDependencies {
logger: Logger;
};
}
export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayProviderOptions> {
protected logger_: Logger;
@ -54,51 +64,31 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
}
}
/**
* Initiates a payment session (e.g. create an order in Razorpay).
* Return a PaymentProviderSessionResponse:
* { session_data: Record<string, unknown> }
*/
async initiatePayment(
context: CreatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Example: create an order in Razorpay
const { currency_code, context: customerDetails } = context;
async initiatePayment(data: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
// Create an order in Razorpay
const { currency_code, amount } = data;
try {
const amount = (context.amount as number) * 100;
// const currency = (data.currency as string) || "INR"
const order = await this.razorpay.orders.create({
amount,
amount: 100 * (amount as number),
currency: currency_code.toUpperCase(),
receipt: `receipt_${Date.now()}`,
payment_capture: true, // auto-capture
});
// Return a PaymentProviderSessionResponse
return {
...order,
id: order.id,
data: {
id: order.id,
...order,
},
};
} catch (e) {
console.log(e);
return {
error: e,
code: 'unknown',
detail: e,
};
throw e;
}
}
/**
* Retrieves the payment session data from Razorpay or from your local records.
* Must return either session_data or throw a PaymentProviderError.
*/
async retrievePayment(
paymentSessionData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
async retrievePayment(data: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
const externalId = data.data.id;
try {
const razorpayOrderId = externalId as string;
if (!razorpayOrderId) {
@ -108,158 +98,95 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
// Example: fetch the order from Razorpay
const order = await this.razorpay.orders.fetch(razorpayOrderId);
return {
...order,
status: order.status || 'created',
id: externalId,
};
return { data: { ...order, status: order.status || 'created', id: externalId } };
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
throw e;
}
}
/**
* Updates the payment session. The signature typically expects:
* (context: UpdatePaymentProviderSession) => Promise<PaymentProviderError | PaymentProviderSessionResponse>
*/
async updatePayment(
context: UpdatePaymentProviderSession,
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
// Deconstruct the data from context
const { amount, currency_code, context: customerDetails, data } = context;
const externalId = data.id;
async updatePayment(data: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
const { amount, currency_code, data: ppiData } = data;
const externalId = ppiData.id;
try {
const response = await this.razorpay.payments.edit(externalId as string, {
notes: {
amount: amount as number,
currency: currency_code,
currency: currency_code.toUpperCase(),
},
});
return {
...response,
data: {
id: response.id,
},
data: { ...response },
};
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
throw e;
}
}
/**
* Authorize the payment (e.g., verify webhook signature, check that payment is successful).
* 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;
async authorizePayment(data: AuthorizePaymentInput): Promise<AuthorizePaymentOutput> {
const externalId = data.data.id;
try {
console.log('authorizePayment externalId', externalId);
// Here you could verify a signature or confirm the payment status in Razorpay
const paymentData = await this.razorpay.orders.fetchPayments(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)
return {
data: {
...paymentSessionData,
id: externalId,
rporder: data.data,
rpauth: paymentData,
id: paymentData.items[0].id,
status: this.rpToMedusaStatus(paymentData.items[0].status),
},
status: PaymentSessionStatus.AUTHORIZED,
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
status: this.rpToMedusaStatus(paymentData.items[0].status),
};
} catch (e) {
throw e;
}
}
/**
* Capture the payment if not auto-captured.
*/
async capturePayment(
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
async capturePayment(data: CapturePaymentInput): Promise<CapturePaymentOutput> {
try {
const externalId = paymentData.id;
const { amount } = paymentData;
if (data.data.status === 'captured') {
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
const newData = await this.razorpay.payments.capture(externalId as string, amount as number, 'INR');
return {
...newData,
// status: "captured",
id: externalId,
};
return { data: { ...newData, id: externalId } };
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
console.log('capturePayment error', e);
throw e;
}
}
/**
* Refund the payment in Razorpay.
*/
async refundPayment(
paymentData: Record<string, unknown>,
refundAmount: number,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentData.id;
async refundPayment(data: RefundPaymentInput): Promise<RefundPaymentOutput> {
const externalId = data.data.id;
try {
const newData = await this.razorpay.payments.refund(externalId as string, { amount: refundAmount });
return {
...newData,
id: externalId,
// refunded_amount: refundAmount,
// status: "refunded",
};
const newData = await this.razorpay.payments.refund(externalId as string, { amount: data.amount as number });
return { data: { ...newData, id: externalId } };
} catch (e) {
return {
error: e,
code: 'unknown',
detail: e,
};
throw e;
}
}
/**
* Cancel a payment if it hasn't been paid. For Razorpay, you may just mark the order as canceled in your system.
*/
async cancelPayment(
paymentData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
async cancelPayment(data: CancelPaymentInput): Promise<CancelPaymentOutput> {
try {
const externalId = paymentData.id;
return {
id: externalId,
status: 'canceled',
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
};
const externalId = data.data.id;
return { data: { id: externalId, status: 'canceled' } };
} catch (e) {
throw e;
}
}
@ -269,23 +196,16 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
* (paymentSessionData: Record<string, unknown>) => Promise<Record<string, unknown> | PaymentProviderError>
* or might require returning PaymentProviderSessionResponse. Adjust if needed.
*/
async deletePayment(
paymentSessionData: Record<string, unknown>,
): Promise<PaymentProviderError | PaymentProviderSessionResponse['data']> {
const externalId = paymentSessionData.id;
async deletePayment(data: DeletePaymentInput): Promise<DeletePaymentOutput> {
const externalId = data.data.id;
try {
// If Razorpay does not allow deleting an order, just mark locally as deleted
// Return the final state or an empty object
return {
id: externalId,
deleted: true,
};
} catch (error) {
return {
error: error,
code: 'unknown',
detail: error,
data: { id: externalId, deleted: true },
};
} catch (e) {
throw e;
}
}
@ -293,11 +213,15 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
* Get the status (PaymentSessionStatus) for the payment.
* 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:
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;
switch (status?.toLowerCase()) {
return { status: this.rpToMedusaStatus(status) };
}
rpToMedusaStatus(rpStatus: string): PaymentSessionStatus {
switch (rpStatus.toLowerCase()) {
case 'created':
return PaymentSessionStatus.PENDING;
case 'authorized':
@ -305,7 +229,7 @@ export class RazorpayPaymentProvider extends AbstractPaymentProvider<RazorpayPro
case 'captured':
return PaymentSessionStatus.CAPTURED;
case 'refunded':
return PaymentSessionStatus.CANCELED; // Payment was authorized before refund
return PaymentSessionStatus.CANCELED;
case 'failed':
return PaymentSessionStatus.ERROR;
default:

5893
yarn.lock

File diff suppressed because it is too large Load Diff