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:
35
package.json
35
package.json
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user