feat: Implement return and refund functionality in commerce module
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:
Sathira Sri Sathara
2026-09-09 12:39:57 +05:30
parent ad9287b804
commit cd2c1c6d08
38 changed files with 192 additions and 4 deletions
+1
View File
@@ -0,0 +1 @@
const state=require("../../app/services/commerce/orderState.service");describe("Phase 7 order state machine",()=>{test.each([["PENDING_PAYMENT","PAID"],["PAID","PROCESSING"],["FULFILLED","COMPLETED"],["REFUND_PENDING","REFUNDED"]])("allows %s -> %s",(from,to)=>expect(state.canTransition(from,to)).toBe(true));test.each([["CANCELLED","PAID"],["REFUNDED","PROCESSING"],["PENDING_PAYMENT","FULFILLED"]])("rejects %s -> %s",(from,to)=>expect(()=>state.assertTransition(from,to)).toThrow(/Illegal order transition/));});
@@ -0,0 +1 @@
const crypto=require("crypto"),provider=require("../../app/services/commerce/providers/payhere.provider");const md5=x=>crypto.createHash("md5").update(x).digest("hex").toUpperCase();describe("PayHere adapter boundary",()=>{beforeEach(()=>process.env.PAYHERE_MERCHANT_SECRET="test-secret");test("verifies the provider hash and parses paid status",()=>{const p={merchant_id:"m",order_id:"ORD-1",payhere_amount:"100.00",payhere_currency:"LKR",status_code:"2",payment_id:"pay-1"};p.md5sig=md5(`${p.merchant_id}${p.order_id}${p.payhere_amount}${p.payhere_currency}${p.status_code}${md5(process.env.PAYHERE_MERCHANT_SECRET)}`);expect(provider.verifyWebhook(p)).toBe(true);expect(provider.parseWebhook(p).status).toBe("PAID");});test("rejects forged hash",()=>expect(provider.verifyWebhook({merchant_id:"m",order_id:"o",payhere_amount:"1.00",payhere_currency:"LKR",status_code:"2",md5sig:"bad"})).toBe(false));});
+1
View File
@@ -0,0 +1 @@
const s=require("../../app/validation/commerce.schemas"),returns=require("../../app/services/commerce/return.service");const input=body=>({body,params:{},query:{}});describe("Phase 7 input boundaries",()=>{test("customer return cannot submit refund amount",()=>expect(()=>s.returnRequest.parse(input({reasonCode:"DAMAGED",items:[{orderItemId:"i",quantity:1,resolution:"REFUND",amount:"999"}]}))).toThrow());test("refund quantities are positive integers",()=>expect(()=>s.refund.parse(input({paymentId:"p",reasonCode:"ADMIN",items:[{orderItemId:"i",quantity:0}]}))).toThrow());test("return window is centralized",()=>expect(returns.windowDays()).toBeGreaterThan(0));});