@@ -50,10 +50,56 @@ WHERE user_id = $1
5050 return err
5151}
5252
53+ // GetOrCreateCart returns the user's current cart (active or checkout_pending)
54+ // or creates a new active cart if none exists
55+ func (r * Repository ) GetOrCreateCart (ctx context.Context , userID int64 ) (int64 , error ) {
56+ var id int64
57+ var status string
58+
59+ // First, try to get ANY current cart (active or checkout_pending)
60+ err := r .db .QueryRow (ctx , `
61+ SELECT id, status
62+ FROM carts
63+ WHERE user_id = $1
64+ AND (status = 'active' OR status = 'checkout_pending')
65+ AND (expires_at IS NULL OR expires_at > now())
66+ ORDER BY
67+ CASE status
68+ WHEN 'checkout_pending' THEN 1
69+ WHEN 'active' THEN 2
70+ END,
71+ updated_at DESC
72+ LIMIT 1
73+ ` , userID ).Scan (& id , & status )
74+
75+ if err == nil {
76+ // Found an existing cart
77+ return id , nil
78+ }
79+
80+ if ! errors .Is (err , pgx .ErrNoRows ) {
81+ // Real DB error
82+ return 0 , fmt .Errorf ("get cart: %w" , err )
83+ }
84+
85+ // No cart exists → create new active cart
86+ exp := time .Now ().Add (r .ttl )
87+ if err := r .db .QueryRow (ctx , `
88+ INSERT INTO carts (user_id, guest_token, status, expires_at)
89+ VALUES ($1, NULL, 'active', $2)
90+ RETURNING id
91+ ` , userID , exp ).Scan (& id ); err != nil {
92+ return 0 , fmt .Errorf ("create cart: %w" , err )
93+ }
94+
95+ return id , nil
96+ }
97+
5398// --- User flows ---
5499
55100// EnsureActive returns an existing active cart id or creates a new one with TTL.
56101// It only sets expires_at when creating a cart; it does NOT bump TTL for existing carts.
102+ // if you want checkout_pending state too use GetOrCreateCart method
57103func (r * Repository ) EnsureActive (ctx context.Context , userID int64 ) (int64 , error ) {
58104 var id int64
59105
@@ -220,26 +266,36 @@ func (r *Repository) UnlockCheckoutCart(ctx context.Context, orderID int64) erro
220266//
221267// We only convert carts that are explicitly linked to the order via checkout_order_id
222268// AND currently in 'checkout_pending'. This prevents converting a wrong cart due to bugs
223- // or race conditions.
269+ // or race conditions. remember db constraint if converted then checkout_order_id=null
224270func (r * Repository ) ConvertCheckoutCart (ctx context.Context , orderID int64 ) error {
225271 _ , err := r .db .Exec (ctx , `
226272 UPDATE carts
227- SET status='converted', updated_at=now()
228- WHERE checkout_order_id=$1 AND status='checkout_pending'
273+ SET status='converted',
274+ checkout_order_id=NULL,
275+ updated_at=now()
276+ WHERE checkout_order_id=$1
277+ AND status='checkout_pending'
229278 ` , orderID )
230279 return err
231280}
232281
233- // Get active cart view by user
282+ // Get active cart or checkout_pending view by user
234283func (r * Repository ) GetView (ctx context.Context , userID int64 ) (* CartView , error ) {
235284 var v CartView
236285
237286 err := r .db .QueryRow (ctx , `
238- SELECT id, user_id, guest_token, status, expires_at, created_at, updated_at
287+ SELECT id, user_id, guest_token, status, expires_at, created_at, updated_at, checkout_order_id
239288FROM carts
240289WHERE user_id = $1
241- AND status = 'active'
290+ AND ( status = 'active' OR status = 'checkout_pending')
242291 AND (expires_at IS NULL OR expires_at > now())
292+ ORDER BY
293+ CASE status
294+ WHEN 'checkout_pending' THEN 1
295+ WHEN 'active' THEN 2
296+ ELSE 3
297+ END,
298+ updated_at DESC
243299LIMIT 1
244300` , userID ).Scan (
245301 & v .Cart .ID ,
@@ -249,6 +305,7 @@ LIMIT 1
249305 & v .Cart .ExpiresAt ,
250306 & v .Cart .CreatedAt ,
251307 & v .Cart .UpdatedAt ,
308+ & v .Cart .CheckoutOrderID ,
252309 )
253310
254311 if err != nil {
@@ -266,7 +323,7 @@ func (r *Repository) GetViewByCartID(ctx context.Context, cartID int64) (*CartVi
266323 var v CartView
267324
268325 if err := r .db .QueryRow (ctx , `
269- SELECT id, user_id, guest_token, status, expires_at, created_at, updated_at
326+ SELECT id, user_id, guest_token, status, expires_at, created_at, updated_at, checkout_order_id
270327FROM carts
271328WHERE id = $1
272329` , cartID ).Scan (
@@ -277,6 +334,7 @@ WHERE id = $1
277334 & v .Cart .ExpiresAt ,
278335 & v .Cart .CreatedAt ,
279336 & v .Cart .UpdatedAt ,
337+ & v .Cart .CheckoutOrderID ,
280338 ); err != nil {
281339 if errors .Is (err , pgx .ErrNoRows ) {
282340 return nil , fmt .Errorf ("cart not found" )
@@ -287,6 +345,7 @@ WHERE id = $1
287345 return r .fillLines (ctx , & v , cartID )
288346}
289347
348+ // fillLines fetches cart items for any cartID, calculates totals directly and return the CartView structure
290349func (r * Repository ) fillLines (ctx context.Context , v * CartView , cartID int64 ) (* CartView , error ) {
291350 rows , err := r .db .Query (ctx , `
292351SELECT
0 commit comments