feat: Implement Phase 6 Shopping and Checkout
- 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.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
"use strict";module.exports={async up(q,S){const ID={type:S.STRING,primaryKey:true,allowNull:false},STR={type:S.STRING},REQ={type:S.STRING,allowNull:false},DATE={type:S.DATE},DEC={type:S.DECIMAL(15,2)},TS={createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}};await q.createTable("carts",{id:ID,user_id:REQ,status:REQ,currency:{type:S.STRING(3),allowNull:false},coupon_code:STR,version:{type:S.INTEGER,allowNull:false,defaultValue:0},expires_at:DATE,...TS});await q.addConstraint("carts",{fields:["user_id","status"],type:"unique",name:"uq_cart_user_status"});await q.createTable("cart_items",{id:ID,cart_id:REQ,variant_id:REQ,quantity:{type:S.INTEGER,allowNull:false},...TS});await q.addConstraint("cart_items",{fields:["cart_id","variant_id"],type:"unique",name:"uq_cart_variant"});await q.createTable("wishlist_items",{id:ID,user_id:REQ,product_id:REQ,...TS});await q.addConstraint("wishlist_items",{fields:["user_id","product_id"],type:"unique",name:"uq_wishlist_product"});
|
||||
await q.createTable("shipping_zones",{id:ID,code:{type:S.STRING(60),allowNull:false,unique:true},name:REQ,status:REQ,duty_mode:REQ,...TS});await q.createTable("shipping_zone_regions",{id:ID,shipping_zone_id:REQ,country_code:{type:S.STRING(2),allowNull:false},province:STR,district:STR,...TS});await q.addIndex("shipping_zone_regions",["country_code","province","district"]);await q.createTable("shipping_methods",{id:ID,code:{type:S.STRING(60),allowNull:false,unique:true},name:REQ,description:{type:S.TEXT},status:REQ,estimated_min_days:{type:S.INTEGER,allowNull:false},estimated_max_days:{type:S.INTEGER,allowNull:false},tracking_supported:{type:S.BOOLEAN,allowNull:false},...TS});await q.createTable("shipping_rates",{id:ID,shipping_zone_id:REQ,shipping_method_id:REQ,currency:{type:S.STRING(3),allowNull:false},base_amount:{...DEC,allowNull:false},free_shipping_threshold:DEC,minimum_subtotal:DEC,maximum_subtotal:DEC,status:REQ,starts_at:DATE,ends_at:DATE,...TS});await q.addIndex("shipping_rates",["shipping_zone_id","shipping_method_id","status"]);
|
||||
await q.createTable("checkout_sessions",{id:ID,checkout_number:{type:S.STRING(80),allowNull:false,unique:true},user_id:REQ,business_customer_id:STR,cart_id:REQ,cart_version:{type:S.INTEGER,allowNull:false},status:REQ,currency:{type:S.STRING(3),allowNull:false},shipping_address_snapshot:{type:S.JSON,allowNull:false},billing_address_snapshot:{type:S.JSON,allowNull:false},shipping_method_id:REQ,shipping_snapshot:{type:S.JSON,allowNull:false},subtotal:{...DEC,allowNull:false},discount_total:{...DEC,allowNull:false},shipping_amount:{...DEC,allowNull:false},tax_amount:{...DEC,allowNull:false},duty_amount:DEC,grand_total:{...DEC,allowNull:false},coupon_code:STR,idempotency_key:{type:S.STRING(160),allowNull:false},request_fingerprint:{type:S.STRING(64),allowNull:false},expires_at:{type:S.DATE,allowNull:false},...TS});await q.addConstraint("checkout_sessions",{fields:["user_id","idempotency_key"],type:"unique",name:"uq_checkout_user_idempotency"});await q.addIndex("checkout_sessions",["status","expires_at"]);await q.createTable("checkout_items",{id:ID,checkout_session_id:REQ,product_id:REQ,variant_id:REQ,reservation_key:{type:S.STRING(160),allowNull:false},sku:{type:S.STRING(100),allowNull:false},product_name:REQ,variant_description:STR,quantity:{type:S.INTEGER,allowNull:false},unit_price:{...DEC,allowNull:false},discount_amount:{...DEC,allowNull:false},line_total:{...DEC,allowNull:false},currency:{type:S.STRING(3),allowNull:false},metadata:{type:S.JSON},...TS});},async down(){throw new Error("Phase 6 migration is forward-only; restore from a verified backup instead");}};
|
||||
Reference in New Issue
Block a user