This commit is contained in:
shafi54 2026-03-25 19:58:40 +05:30
parent 3c836e274d
commit 4414f9f64b
6 changed files with 81 additions and 199 deletions

View file

@ -63,7 +63,8 @@ export default function OrderDetails() {
onSuccess: (result) => { onSuccess: (result) => {
Alert.alert( Alert.alert(
"Success", "Success",
`Refund initiated successfully!\n\nAmount: ₹${result.amount}\nStatus: ${result.status}` `Refund initiated successfully!\n\nAmount: `
// `Refund initiated successfully!\n\nAmount: ₹${result.amount}\nStatus: ${result.status}`
); );
setInitiateRefundDialogOpen(false); setInitiateRefundDialogOpen(false);
}, },

View file

@ -2,7 +2,6 @@ import * as cron from 'node-cron';
import { db } from '@/src/db/db_index' import { db } from '@/src/db/db_index'
import { payments, orders, deliverySlotInfo, refunds } from '@/src/db/schema' import { payments, orders, deliverySlotInfo, refunds } from '@/src/db/schema'
import { eq, and, gt, isNotNull } from 'drizzle-orm'; import { eq, and, gt, isNotNull } from 'drizzle-orm';
import { RazorpayPaymentService } from '@/src/lib/payments-utils'
interface PendingPaymentRecord { interface PendingPaymentRecord {
payment: typeof payments.$inferSelect; payment: typeof payments.$inferSelect;
@ -20,34 +19,34 @@ export const createPaymentNotification = (record: PendingPaymentRecord) => {
export const checkRefundStatuses = async () => { export const checkRefundStatuses = async () => {
try { try {
const initiatedRefunds = await db // const initiatedRefunds = await db
.select() // .select()
.from(refunds) // .from(refunds)
.where(and( // .where(and(
eq(refunds.refundStatus, 'initiated'), // eq(refunds.refundStatus, 'initiated'),
isNotNull(refunds.merchantRefundId) // isNotNull(refunds.merchantRefundId)
)); // ));
//
// Process refunds concurrently using Promise.allSettled // // Process refunds concurrently using Promise.allSettled
const promises = initiatedRefunds.map(async (refund) => { // const promises = initiatedRefunds.map(async (refund) => {
if (!refund.merchantRefundId) return; // if (!refund.merchantRefundId) return;
//
try { // try {
const razorpayRefund = await RazorpayPaymentService.fetchRefund(refund.merchantRefundId); // const razorpayRefund = await RazorpayPaymentService.fetchRefund(refund.merchantRefundId);
//
if (razorpayRefund.status === 'processed') { // if (razorpayRefund.status === 'processed') {
await db // await db
.update(refunds) // .update(refunds)
.set({ refundStatus: 'success', refundProcessedAt: new Date() }) // .set({ refundStatus: 'success', refundProcessedAt: new Date() })
.where(eq(refunds.id, refund.id)); // .where(eq(refunds.id, refund.id));
} // }
} catch (error) { // } catch (error) {
console.error(`Error checking refund ${refund.id}:`, error); // console.error(`Error checking refund ${refund.id}:`, error);
} // }
}); // });
//
// Wait for all promises to complete // // Wait for all promises to complete
await Promise.allSettled(promises); // await Promise.allSettled(promises);
} catch (error) { } catch (error) {
console.error('Error in checkRefundStatuses:', error); console.error('Error in checkRefundStatuses:', error);
} }

View file

@ -1,4 +1,4 @@
import Razorpay from "razorpay"; // import Razorpay from "razorpay";
import { razorpayId, razorpaySecret } from "@/src/lib/env-exporter" import { razorpayId, razorpaySecret } from "@/src/lib/env-exporter"
import { db } from "@/src/db/db_index" import { db } from "@/src/db/db_index"
import { payments } from "@/src/db/schema" import { payments } from "@/src/db/schema"
@ -6,54 +6,54 @@ import { payments } from "@/src/db/schema"
type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0]; type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0];
export class RazorpayPaymentService { export class RazorpayPaymentService {
private static instance = new Razorpay({ // private static instance = new Razorpay({
key_id: razorpayId, // key_id: razorpayId,
key_secret: razorpaySecret, // key_secret: razorpaySecret,
}); // });
//
static async createOrder(orderId: number, amount: string) { static async createOrder(orderId: number, amount: string) {
// Create Razorpay order // Create Razorpay order
const razorpayOrder = await this.instance.orders.create({ // const razorpayOrder = await this.instance.orders.create({
amount: parseFloat(amount) * 100, // Convert to paisa // amount: parseFloat(amount) * 100, // Convert to paisa
currency: 'INR', // currency: 'INR',
receipt: `order_${orderId}`, // receipt: `order_${orderId}`,
notes: { // notes: {
customerOrderId: orderId.toString(), // customerOrderId: orderId.toString(),
}, // },
}); // });
//
return razorpayOrder; // return razorpayOrder;
} }
static async insertPaymentRecord(orderId: number, razorpayOrder: any, tx?: Tx) { static async insertPaymentRecord(orderId: number, razorpayOrder: any, tx?: Tx) {
// Use transaction if provided, otherwise use db // Use transaction if provided, otherwise use db
const dbInstance = tx || db; // const dbInstance = tx || db;
//
// Insert payment record // // Insert payment record
const [payment] = await dbInstance // const [payment] = await dbInstance
.insert(payments) // .insert(payments)
.values({ // .values({
status: 'pending', // status: 'pending',
gateway: 'razorpay', // gateway: 'razorpay',
orderId, // orderId,
token: orderId.toString(), // token: orderId.toString(),
merchantOrderId: razorpayOrder.id, // merchantOrderId: razorpayOrder.id,
payload: razorpayOrder, // payload: razorpayOrder,
}) // })
.returning(); // .returning();
//
return payment; // return payment;
} }
static async initiateRefund(paymentId: string, amount: number) { static async initiateRefund(paymentId: string, amount: number) {
const refund = await this.instance.payments.refund(paymentId, { // const refund = await this.instance.payments.refund(paymentId, {
amount, // amount,
}); // });
return refund; // return refund;
} }
static async fetchRefund(refundId: string) { static async fetchRefund(refundId: string) {
const refund = await this.instance.refunds.fetch(refundId); // const refund = await this.instance.refunds.fetch(refundId);
return refund; // return refund;
} }
} }

View file

@ -1,15 +1,5 @@
import { router, protectedProcedure } from "@/src/trpc/trpc-index" import { router, protectedProcedure } from "@/src/trpc/trpc-index"
import { z } from "zod"; import { z } from "zod";
import { db } from "@/src/db/db_index"
import {
orders,
orderStatus,
payments,
refunds,
} from "@/src/db/schema";
import { and, eq } from "drizzle-orm";
import { ApiError } from "@/src/lib/api-error"
import { RazorpayPaymentService } from "@/src/lib/payments-utils"
const initiateRefundSchema = z const initiateRefundSchema = z
.object({ .object({
@ -33,114 +23,6 @@ export const adminPaymentsRouter = router({
initiateRefund: protectedProcedure initiateRefund: protectedProcedure
.input(initiateRefundSchema) .input(initiateRefundSchema)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
try { return {}
const { orderId, refundPercent, refundAmount } = input;
// Validate order exists
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
});
if (!order) {
throw new ApiError("Order not found", 404);
}
// Check if order is paid
const orderStatusRecord = await db.query.orderStatus.findFirst({
where: eq(orderStatus.orderId, orderId),
});
if(order.isCod) {
throw new ApiError("Order is a Cash On Delivery. Not eligible for refund")
}
if (
!orderStatusRecord ||
(orderStatusRecord.paymentStatus !== "success" &&
!(order.isCod && orderStatusRecord.isDelivered))
) {
throw new ApiError("Order payment not verified or not eligible for refund", 400);
}
// Calculate refund amount
let calculatedRefundAmount: number;
if (refundPercent !== undefined) {
calculatedRefundAmount =
(parseFloat(order.totalAmount) * refundPercent) / 100;
} else if (refundAmount !== undefined) {
calculatedRefundAmount = refundAmount;
if (calculatedRefundAmount > parseFloat(order.totalAmount)) {
throw new ApiError("Refund amount cannot exceed order total", 400);
}
} else {
throw new ApiError("Invalid refund parameters", 400);
}
let razorpayRefund = null;
let merchantRefundId = null;
// Get payment record for online payments
const payment = await db.query.payments.findFirst({
where: and(
eq(payments.orderId, orderId),
eq(payments.status, "success")
),
});
if (!payment || payment.status !== "success") {
throw new ApiError("Payment not found or not successful", 404);
}
const payload = payment.payload as any;
// Initiate Razorpay refund
razorpayRefund = await RazorpayPaymentService.initiateRefund(
payload.payment_id,
Math.round(calculatedRefundAmount * 100) // Convert to paisa
);
merchantRefundId = razorpayRefund.id;
// Check if refund already exists for this order
const existingRefund = await db.query.refunds.findFirst({
where: eq(refunds.orderId, orderId),
});
const refundStatus = "initiated";
if (existingRefund) {
// Update existing refund
await db
.update(refunds)
.set({
refundAmount: calculatedRefundAmount.toString(),
refundStatus,
merchantRefundId,
refundProcessedAt: order.isCod ? new Date() : null,
})
.where(eq(refunds.id, existingRefund.id));
} else {
// Insert new refund
await db
.insert(refunds)
.values({
orderId,
refundAmount: calculatedRefundAmount.toString(),
refundStatus,
merchantRefundId,
});
}
return {
refundId: merchantRefundId || `cod_${orderId}`,
amount: calculatedRefundAmount,
status: refundStatus,
message: order.isCod ? "COD refund processed successfully" : "Refund initiated successfully",
};
}
catch(e) {
console.log(e);
throw new ApiError("Failed to initiate refund")
}
}), }),
}); });

View file

@ -23,7 +23,6 @@ import {
sendOrderPlacedNotification, sendOrderPlacedNotification,
sendOrderCancelledNotification, sendOrderCancelledNotification,
} from "@/src/lib/notif-job"; } from "@/src/lib/notif-job";
import { RazorpayPaymentService } from "@/src/lib/payments-utils";
import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common"; import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common";
import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store"; import { CONST_KEYS, getConstant, getConstants } from "@/src/lib/const-store";
import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler"; import { publishFormattedOrder, publishCancellation } from "@/src/lib/post-order-handler";
@ -316,15 +315,15 @@ const placeOrderUtil = async (params: {
await tx.insert(orderStatus).values(allOrderStatuses); await tx.insert(orderStatus).values(allOrderStatuses);
if (paymentMethod === "online" && sharedPaymentInfoId) { if (paymentMethod === "online" && sharedPaymentInfoId) {
const razorpayOrder = await RazorpayPaymentService.createOrder( // const razorpayOrder = await RazorpayPaymentService.createOrder(
sharedPaymentInfoId, // sharedPaymentInfoId,
totalWithDelivery.toString() // totalWithDelivery.toString()
); // );
await RazorpayPaymentService.insertPaymentRecord( // await RazorpayPaymentService.insertPaymentRecord(
sharedPaymentInfoId, // sharedPaymentInfoId,
razorpayOrder, // razorpayOrder,
tx // tx
); // );
} }
return insertedOrders; return insertedOrders;

View file

@ -52,7 +52,8 @@ export const paymentRouter = router({
await RazorpayPaymentService.insertPaymentRecord(parseInt(orderId), razorpayOrder); await RazorpayPaymentService.insertPaymentRecord(parseInt(orderId), razorpayOrder);
return { return {
razorpayOrderId: razorpayOrder.id, razorpayOrderId: 0,
// razorpayOrderId: razorpayOrder.id,
key: razorpayId, key: razorpayId,
}; };
}), }),