feat: Implement return and refund functionality in commerce module
CI / test (push) Successful in 10m27s
CI / test (push) Successful in 10m27s
- Added return request handling in return.controller.js - Implemented webhook handling for PayHere payments in webhook.controller.js - Created models for coupon redemptions, invoices, orders, order items, payments, payment attempts, payment webhook events, refunds, return items, and return requests. - Developed services for order management, payment processing, refunds, and returns. - Introduced validation schemas for payment and return requests. - Created migration scripts for new database tables related to orders, payments, refunds, and returns. - Added unit tests for order state transitions and PayHere provider functionality.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
const crypto=require("crypto"),db=require("../../models"),inventory=require("../inventory/inventory.service"),{nextSequence,pad}=require("../../utils/referenceNumber.util");
|
||||
const fault=(m,c,s=400)=>Object.assign(new Error(m),{code:c,status:s}),windowDays=()=>Number(process.env.RETURN_WINDOW_DAYS||30);
|
||||
async function request({orderId,userId,items,reasonCode,reasonText}){return db.sequelize.transaction(async transaction=>{const order=await db.Order.findOne({where:{id:orderId,user_id:userId,status:["PAID","PROCESSING","READY_FOR_FULFILLMENT","FULFILLED","COMPLETED","PARTIALLY_REFUNDED"]},transaction,lock:transaction.LOCK.UPDATE});if(!order||Date.now()-new Date(order.placed_at).getTime()>windowDays()*86400000)throw fault("Order is not return eligible","RETURN_NOT_ELIGIBLE",409);const rows=[];for(const input of items){const item=await db.OrderItem.findOne({where:{id:input.orderItemId,order_id:order.id},transaction,lock:transaction.LOCK.UPDATE});if(!item||input.quantity<=0||item.returned_quantity+input.quantity>item.quantity)throw fault("Return quantity exceeds remaining quantity","INVALID_RETURN_QUANTITY",409);rows.push({item,input});}const row=await db.ReturnRequest.create({id:crypto.randomUUID(),return_number:`RMA-${new Date().getUTCFullYear()}-${pad(await nextSequence("returns",transaction),6)}`,order_id:order.id,user_id:userId,status:"REQUESTED",reason_code:reasonCode,reason_text:reasonText,requested_at:new Date()},{transaction});await db.ReturnItem.bulkCreate(rows.map(x=>({id:crypto.randomUUID(),return_request_id:row.id,order_item_id:x.item.id,quantity:x.input.quantity,resolution:x.input.resolution,condition:"PENDING"})),{transaction});return row;});}
|
||||
async function transition({id,status,actorUserId,conditions=[]}){return db.sequelize.transaction(async transaction=>{const row=await db.ReturnRequest.findByPk(id,{transaction,lock:transaction.LOCK.UPDATE});if(!row)throw fault("Return not found","NOT_FOUND",404);const allowed={REQUESTED:["APPROVED","REJECTED","CANCELLED"],APPROVED:["RECEIVED"],AWAITING_RETURN:["RECEIVED"],RECEIVED:["COMPLETED"]};if(!allowed[row.status]?.includes(status))throw fault("Illegal return transition","ILLEGAL_RETURN_TRANSITION",409);await row.update({status,...(status==="APPROVED"&&{approved_at:new Date()}),...(status==="RECEIVED"&&{received_at:new Date()}),...(status==="COMPLETED"&&{completed_at:new Date()})},{transaction});if(status==="COMPLETED"){const items=await db.ReturnItem.findAll({where:{return_request_id:row.id},transaction,lock:transaction.LOCK.UPDATE});for(const item of items){const decision=conditions.find(x=>x.returnItemId===item.id),condition=decision?.condition||item.condition;await item.update({condition},{transaction});const orderItem=await db.OrderItem.findByPk(item.order_item_id,{transaction,lock:transaction.LOCK.UPDATE});await orderItem.increment("returned_quantity",{by:item.quantity,transaction});if(condition==="RESTOCKABLE"){if(!decision?.warehouseId)throw fault("Warehouse is required for restock","WAREHOUSE_REQUIRED");await inventory.adjustStock({eventId:`return:${row.id}:${item.id}`,warehouseId:decision.warehouseId,variantId:orderItem.variant_id,quantityDelta:item.quantity,reason:"Accepted return receipt",actorUserId,requestId:`return:${row.id}`,transaction});}}}return row;});}
|
||||
module.exports={request,transition,windowDays};
|
||||
Reference in New Issue
Block a user