ad9287b804
- Introduced authenticated shopping state and atomic checkout process without creating orders or payments. - Reused existing components from previous phases including identities, addresses, catalogues, pricing, and inventory. - Developed cart architecture to support one active cart per user with unique items per variant. - Implemented dynamic cart pricing with various precedence rules for promotions and coupons. - Created a wishlist feature that exposes only visible active products without reserving inventory. - Integrated inventory validation to ensure availability of items before adding to cart. - Developed a checkout architecture that locks the cart, revalidates pricing and shipping, and reserves inventory. - Added shipping zones, methods, and rates with configurable options for international shipping and duty/tax boundaries. - Implemented checkout snapshots to capture immutable checkout details. - Introduced idempotency for checkout requests to prevent duplicate processing. - Added functionality for coupon integration and business checkout validation. - Established permissions for managing shipping zones, methods, and rates. - Created comprehensive unit tests covering various aspects of the shopping and checkout processes. - Added cron job for handling checkout expiry reconciliation. - Created migration script to set up new database tables and constraints for carts, checkout sessions, and shipping.
4 lines
1.7 KiB
JavaScript
4 lines
1.7 KiB
JavaScript
const {Op}=require("sequelize"),db=require("../../models"),money=require("../pricing/money");const fault=(m,c,s=400)=>Object.assign(new Error(m),{code:c,status:s});
|
|
async function getAvailableMethods({address,subtotal,currency="LKR",at=new Date()}){const regions=await db.ShippingZoneRegion.findAll({where:{country_code:String(address.country_code).toUpperCase(),[Op.and]:[{[Op.or]:[{province:null},{province:address.province}]},{[Op.or]:[{district:null},{district:address.district}]}]},order:[["province","DESC"],["district","DESC"]]});if(!regions.length)throw fault("Destination is not supported","UNSUPPORTED_DESTINATION",409);const zone=await db.ShippingZone.findOne({where:{id:regions[0].shipping_zone_id,status:"ACTIVE"}});if(!zone)throw fault("Destination is not supported","UNSUPPORTED_DESTINATION",409);const rates=await db.ShippingRate.findAll({where:{shipping_zone_id:zone.id,currency,status:"ACTIVE",[Op.and]:[{[Op.or]:[{starts_at:null},{starts_at:{[Op.lte]:at}}]},{[Op.or]:[{ends_at:null},{ends_at:{[Op.gt]:at}}]}]},include:[{model:db.ShippingMethod,as:"method",where:{status:"ACTIVE"}}]});return rates.filter(r=>(!r.minimum_subtotal||money.parse(subtotal)>=money.parse(r.minimum_subtotal))&&(!r.maximum_subtotal||money.parse(subtotal)<=money.parse(r.maximum_subtotal))).map(r=>{const free=Boolean(r.free_shipping_threshold&&money.parse(subtotal)>=money.parse(r.free_shipping_threshold));return {shippingRateId:r.id,methodId:r.method.id,code:r.method.code,name:r.method.name,amount:free?"0.00":String(r.base_amount),freeShippingApplied:free,estimatedMinDays:r.method.estimated_min_days,estimatedMaxDays:r.method.estimated_max_days,zone:{code:zone.code,name:zone.name},dutyMode:zone.duty_mode};});}
|
|
module.exports={getAvailableMethods};
|