abb760cc19
CI / test (push) Successful in 10m23s
- Added models for loyalty points allocation, redemption, rewards, tiers, tier history, and referrals. - Created business credit ledger entries, settlements, and settlement items models. - Developed services for loyalty operations including account management, point allocation, redemption, and referral handling. - Implemented wholesale credit management services for transactions and settlements. - Established routes for loyalty and wholesale admin and customer operations with appropriate middleware for authentication and permission checks. - Introduced validation schemas for loyalty and wholesale operations. - Set up cron jobs for loyalty reconciliation tasks such as birthday rewards and point expirations. - Created migration scripts to set up the database schema for loyalty and wholesale features. - Added unit tests for validation and policy enforcement in loyalty and wholesale services.
2 lines
2.1 KiB
JavaScript
2 lines
2.1 KiB
JavaScript
const crypto=require("crypto"),db=require("../../models"),money=require("../pricing/money");const fault=(m,c,s=400)=>Object.assign(new Error(m),{code:c,status:s});async function balance(accountId,transaction){const last=await db.BusinessCreditLedgerEntry.findOne({where:{business_credit_account_id:accountId},order:[["occurred_at","DESC"],["createdAt","DESC"]],transaction,lock:transaction&&transaction.LOCK.UPDATE});return last?String(last.balance_after):"0.00";}async function post({businessCustomerId,eventId,type,amount,currency,orderId,paymentId,reference,actorUserId},external){const run=async transaction=>{const prior=await db.BusinessCreditLedgerEntry.findOne({where:{event_id:eventId},transaction,lock:transaction.LOCK.UPDATE});if(prior)return{entry:prior,idempotent:true};const business=await db.BusinessCustomer.findOne({where:{business_customer_id:businessCustomerId,status:"ACTIVE"},transaction}),account=business&&await db.BusinessCreditAccount.findOne({where:{business_profile_id:businessCustomerId,status:"ACTIVE",currency},transaction,lock:transaction.LOCK.UPDATE});if(!account)throw fault("Active matching credit account required","CREDIT_UNAVAILABLE",409);const current=await balance(account.id,transaction),positive=["AUTHORIZATION","CAPTURE","ADJUSTMENT"].includes(type),delta=positive?String(amount):`-${String(amount)}`,next=positive?money.add(current,String(amount)):money.subtractFloor(current,String(amount));if(positive&&money.parse(next)>money.parse(account.credit_limit))throw fault("Insufficient business credit","INSUFFICIENT_CREDIT",409);const entry=await db.BusinessCreditLedgerEntry.create({id:crypto.randomUUID(),event_id:eventId,business_credit_account_id:account.id,business_customer_id:businessCustomerId,order_id:orderId,payment_id:paymentId,type,amount:String(amount),currency,balance_delta:delta,balance_after:next,reference,occurred_at:new Date(),metadata:{actorUserId}},{transaction});return{entry,idempotent:false,creditLimit:String(account.credit_limit),usedCredit:next,availableCredit:money.subtractFloor(account.credit_limit,next)};};return external?run(external):db.sequelize.transaction(run);}module.exports={balance,post};
|