Files
insforge--insforge/backend/tests/unit/razorpay-order.service.test.ts
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

203 lines
5.5 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const {
mockPoolQuery,
mockUserClientQuery,
mockCreateOrder,
mockVerifyOrderPaymentSignature,
mockCreateRazorpayProvider,
mockWithUserContext,
} = vi.hoisted(() => ({
mockPoolQuery: vi.fn(),
mockUserClientQuery: vi.fn(),
mockCreateOrder: vi.fn(),
mockVerifyOrderPaymentSignature: vi.fn(),
mockCreateRazorpayProvider: vi.fn(),
mockWithUserContext: vi.fn(),
}));
vi.mock('../../src/infra/database/database.manager', () => ({
DatabaseManager: {
getInstance: () => ({
getPool: () => ({
query: mockPoolQuery,
}),
}),
},
}));
vi.mock('../../src/services/database/user-context.service', () => ({
withUserContext: mockWithUserContext,
}));
vi.mock('../../src/services/payments/razorpay/config.service', () => ({
RazorpayConfigService: {
getInstance: () => ({
createRazorpayProvider: mockCreateRazorpayProvider,
}),
},
}));
import { RazorpayOrderService } from '../../src/services/payments/razorpay/order.service';
function buildOrderRow(overrides: Record<string, unknown> = {}) {
return {
id: 'local_order_123',
environment: 'test',
status: 'created',
subjectType: 'team',
subjectId: 'team_123',
customerName: 'Buyer',
customerEmail: 'buyer@example.com',
customerContact: '+919999999999',
orderId: 'order_123',
receipt: 'receipt_123',
amount: 50000,
amountPaid: 0,
amountDue: 50000,
currency: 'inr',
attempts: 0,
verifiedPaymentId: null,
verifiedAt: null,
lastError: null,
createdAt: new Date('2026-01-01T00:00:00Z'),
updatedAt: new Date('2026-01-01T00:00:00Z'),
...overrides,
};
}
describe('RazorpayOrderService', () => {
beforeEach(() => {
vi.clearAllMocks();
mockWithUserContext.mockImplementation(async (_pool, _user, task) =>
task({ query: mockUserClientQuery })
);
mockCreateRazorpayProvider.mockResolvedValue({
getKeyId: () => 'rzp_test_key',
createOrder: mockCreateOrder,
verifyOrderPaymentSignature: mockVerifyOrderPaymentSignature,
});
});
it('creates a native Razorpay order with subject notes for Checkout', async () => {
mockUserClientQuery.mockResolvedValueOnce({ rowCount: 1, rows: [] });
mockCreateOrder.mockResolvedValue({
id: 'order_123',
entity: 'order',
amount: 50000,
amount_paid: 0,
amount_due: 50000,
currency: 'INR',
receipt: 'receipt_123',
status: 'created',
attempts: 0,
notes: {},
created_at: 1767225600,
});
mockPoolQuery.mockResolvedValueOnce({ rows: [buildOrderRow()], rowCount: 1 });
const result = await RazorpayOrderService.getInstance().createOrder(
{
environment: 'test',
amount: 50000,
currency: 'INR',
receipt: 'receipt_123',
description: 'Pro upgrade',
subject: { type: 'team', id: 'team_123' },
customerName: 'Buyer',
customerEmail: 'buyer@example.com',
customerContact: '+919999999999',
},
{ role: 'authenticated', id: 'user_123', email: 'buyer@example.com' }
);
expect(mockUserClientQuery).toHaveBeenCalledWith(
expect.stringMatching(/INSERT INTO payments\.razorpay_orders/i),
expect.arrayContaining([
'test',
'team',
'team_123',
'Buyer',
'buyer@example.com',
'+919999999999',
'receipt_123',
50000,
'inr',
])
);
expect(mockCreateOrder).toHaveBeenCalledWith(
expect.objectContaining({
amount: 50000,
currency: 'INR',
receipt: 'receipt_123',
notes: expect.objectContaining({
insforge_subject_type: 'team',
insforge_subject_id: 'team_123',
insforge_order_id: expect.any(String),
}),
})
);
expect(result.checkoutOptions).toEqual(
expect.objectContaining({
key: 'rzp_test_key',
amount: 50000,
currency: 'INR',
order_id: 'order_123',
description: 'Pro upgrade',
})
);
});
it('verifies the Razorpay Checkout signature before marking an order verified', async () => {
mockVerifyOrderPaymentSignature.mockReturnValue(true);
mockPoolQuery.mockResolvedValueOnce({
rows: [
buildOrderRow({
status: 'attempted',
verifiedPaymentId: 'pay_123',
verifiedAt: new Date('2026-01-01T00:01:00Z'),
}),
],
rowCount: 1,
});
const result = await RazorpayOrderService.getInstance().verifyOrderPayment({
environment: 'test',
orderId: 'order_123',
paymentId: 'pay_123',
signature: 'signature',
});
expect(mockVerifyOrderPaymentSignature).toHaveBeenCalledWith(
'order_123',
'pay_123',
'signature'
);
expect(mockPoolQuery).toHaveBeenCalledWith(
expect.stringMatching(/UPDATE payments\.razorpay_orders/i),
['test', 'order_123', 'pay_123']
);
expect(result).toEqual(
expect.objectContaining({
verified: true,
order: expect.objectContaining({ verifiedPaymentId: 'pay_123' }),
})
);
});
it('rejects invalid Razorpay Checkout signatures without updating the order', async () => {
mockVerifyOrderPaymentSignature.mockReturnValue(false);
await expect(
RazorpayOrderService.getInstance().verifyOrderPayment({
environment: 'test',
orderId: 'order_123',
paymentId: 'pay_123',
signature: 'bad',
})
).rejects.toThrow(/Invalid Razorpay payment signature/);
expect(mockPoolQuery).not.toHaveBeenCalled();
});
});