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 @@
|
||||
const crypto=require("crypto"),db=require("../../models"),money=require("../pricing/money"),{nextSequence,pad}=require("../../utils/referenceNumber.util");const fault=(m,c,s=400)=>Object.assign(new Error(m),{code:c,status:s});async function request({orderId,paymentId,items,reasonCode,reasonText,actorUserId,operationKey}){return db.sequelize.transaction(async transaction=>{const prior=await db.Refund.findOne({where:{operation_key:operationKey},transaction,lock:transaction.LOCK.UPDATE});if(prior)return{refund:prior,idempotent:true};const order=await db.Order.findByPk(orderId,{transaction,lock:transaction.LOCK.UPDATE}),payment=await db.Payment.findOne({where:{id:paymentId,order_id:orderId,status:["PAID","PARTIALLY_REFUNDED"]},transaction,lock:transaction.LOCK.UPDATE});if(!order||!payment)throw fault("Refundable payment not found","NOT_REFUNDABLE",409);let total="0.00";const rows=[];for(const input of items){const item=await db.OrderItem.findOne({where:{id:input.orderItemId,order_id:orderId},transaction,lock:transaction.LOCK.UPDATE});if(!item||input.quantity<=0||item.refunded_quantity+input.quantity>item.quantity)throw fault("Refund quantity exceeds purchased quantity","INVALID_REFUND_QUANTITY",409);const amount=money.multiply(item.unit_price,input.quantity);total=money.add(total,amount);rows.push({item,input,amount});}const completed=await db.Refund.sum("amount",{where:{payment_id:payment.id,status:"COMPLETED"},transaction})||"0.00";if(money.parse(total)+money.parse(completed)>money.parse(payment.amount))throw fault("Refund exceeds captured amount","REFUND_EXCEEDS_PAYMENT",409);const refund=await db.Refund.create({id:crypto.randomUUID(),refund_number:`REF-${new Date().getUTCFullYear()}-${pad(await nextSequence("refunds",transaction),6)}`,operation_key:operationKey,order_id:orderId,payment_id:paymentId,status:"REQUESTED",amount:total,currency:order.currency,reason_code:reasonCode,reason_text:reasonText,requested_by:actorUserId,requested_at:new Date()},{transaction});await db.RefundItem.bulkCreate(rows.map(x=>({id:crypto.randomUUID(),refund_id:refund.id,order_item_id:x.item.id,quantity:x.input.quantity,amount:x.amount})),{transaction});await order.update({status:"REFUND_PENDING"},{transaction});return{refund,idempotent:false};});}module.exports={request};
|
||||
Reference in New Issue
Block a user