--- before/public_html/app/checkout-v40.php +++ after/public_html/app/checkout-v40.php @@ -9,9 +9,10 @@ } function av40_items(PDO $pdo,bool $lock=false):array { $result=[]; $total=0.;$weight=0;$cart=$_SESSION['cart']??[]; + ksort($cart,SORT_STRING); // Stable lock order reduces concurrent checkout deadlocks. foreach($cart as $key=>$quantity){ - if(!preg_match('/^[0-9]+:[0-9]+$/',(string)$key))throw new RuntimeException('Invalid cart entry.'); - [$id,$vid]=array_map('intval',explode(':',$key));$qty=(int)$quantity; + if(!preg_match('/^[1-9][0-9]*:(?:0|[1-9][0-9]*)$/D',(string)$key))throw new RuntimeException('Invalid cart entry.'); + [$id,$vid]=array_map('intval',explode(':',$key));$validQty=filter_var($quantity,FILTER_VALIDATE_INT);$qty=$validQty===false?0:(int)$validQty; if($qty<1||$qty>99)throw new RuntimeException('Quantity must be between 1 and 99.'); $s=$pdo->prepare("SELECT * FROM products WHERE id=? AND status='published' AND is_active=1".($lock?' FOR UPDATE':''));$s->execute([$id]);$p=$s->fetch();if(!$p)throw new RuntimeException('A product is unavailable.'); $v=null;if($p['type']!=='single'){$s=$pdo->prepare('SELECT * FROM variants WHERE id=? AND product_id=? AND is_active=1'.($lock?' FOR UPDATE':''));$s->execute([$vid,$id]);$v=$s->fetch();if(!$v)throw new RuntimeException('Please select a valid product variant.');}elseif($vid!==0)throw new RuntimeException('Invalid item option.'); @@ -28,7 +29,7 @@ $code=strtoupper(trim($code));if($code==='')return 0.; $s=$pdo->prepare('SELECT * FROM av_coupons WHERE code=? AND active=1 AND (expires_at IS NULL OR expires_at>NOW())');$s->execute([$code]);$c=$s->fetch(); if(!$c||$subtotal<(float)$c['min_subtotal'])throw new RuntimeException('Coupon is invalid or requirements are not met.'); - return round(min($subtotal, $c['type']==='fixed'?(float)$c['value']:$subtotal*min(100,(float)$c['value'])/100),2); + return round(max(0,min($subtotal, $c['type']==='fixed'?(float)$c['value']:$subtotal*min(100,(float)$c['value'])/100)),2); } function av40_delivery(PDO $pdo,int $courierId,string $district,string $method,int $weight):array { $district=trim($district);if($district==='')return ['known'=>false,'base'=>null,'extra'=>null,'eta'=>'','cod'=>false];if($method!=='home'&&$method!=='branch')throw new RuntimeException('Invalid delivery method.'); --- before/public_html/app/confirmation-v52.php +++ after/public_html/app/confirmation-v52.php @@ -12,7 +12,7 @@ $title=htmlspecialchars($headline,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8'); $message=htmlspecialchars($detail,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8'); $reference=$ref?'

Support reference: '.htmlspecialchars($ref,ENT_QUOTES,'UTF-8').'

':''; - echo ''.$title.' | AVOLEEN

'.$title.'

'.$message.'

'.$reference.'Back to Store
'; + echo ''.$title.' | AVOLEEN

'.$title.'

'.$message.'

'.$reference.'Back to Store
'; exit; } function av52_value(array $row,string $key,$fallback=''){return array_key_exists($key,$row)&&$row[$key]!==null?$row[$key]:$fallback;} @@ -38,7 +38,7 @@ $s=strtolower((string)av52_value($order,'status','')); if(in_array($s,['cancelled','canceled','returned','refunded'],true))return ucfirst($s); $f=strtolower((string)av52_value($order,'fulfillment_status','')); - if(in_array($f,['confirmed','processing','shipped','out_for_delivery','delivered'],true))return ucwords(str_replace('_',' ',$f)); + if(in_array($f,['confirmed','processing','ready_to_ship','shipped','out_for_delivery','delivered','on_hold','delivery_failed'],true))return ucwords(str_replace('_',' ',$f)); return 'Order Received'; } function av52_time(string $date):string { --- before/public_html/app/order-read-v53.php +++ after/public_html/app/order-read-v53.php @@ -8,7 +8,7 @@ function av53_headers():void {if(!headers_sent()){header('Cache-Control: private, no-store, max-age=0');header('Referrer-Policy: no-referrer');header('X-Robots-Tag: noindex, nofollow, noarchive');header('X-Content-Type-Options: nosniff');}} function av53_error(int $code,string $title,string $message,string $ref=''):never { if(!headers_sent()){http_response_code($code);header('Content-Type: text/html; charset=utf-8');} - $back=function_exists('url')?url('index.php'):'/avoleen-v30/index.php'; + $back=function_exists('url')?url('index.php'):'/index.php'; echo ''.htmlspecialchars($title,ENT_QUOTES,'UTF-8').' | AVOLEEN

'.htmlspecialchars($title,ENT_QUOTES,'UTF-8').'

'.htmlspecialchars($message,ENT_QUOTES,'UTF-8').'

'.($ref?'Support reference: '.htmlspecialchars($ref,ENT_QUOTES,'UTF-8').'':'').'

Back to Home

';exit; } function av53_read(PDO $pdo,string $sql,array $params,string $ref,string $stage,int $orderId,bool $optional=false):array { @@ -32,7 +32,7 @@ function av53_order_status(array $o):string { $status=strtolower((string)($o['status']??''));if(in_array($status,['cancelled','canceled','returned','refunded'],true))return ucfirst($status); $f=strtolower((string)($o['fulfillment_status']??'')); - return match($f){'confirmed'=>'Confirmed','processing'=>'Processing','shipped'=>'Shipped','out_for_delivery'=>'Out for Delivery','delivered'=>'Delivered',default=>'Order Received'}; + return match($f){'confirmed'=>'Confirmed','processing'=>'Processing','ready_to_ship'=>'Ready to Ship','on_hold'=>'On Hold','delivery_failed'=>'Delivery Failed','shipped'=>'Shipped','out_for_delivery'=>'Out for Delivery','delivered'=>'Delivered',default=>'Order Received'}; } function av53_public_id(array $o,int $id):string {$v=trim((string)($o['public_order_id']??''));return $v!==''?$v:'avo-'.str_pad((string)$id,5,'0',STR_PAD_LEFT);} function av53_date($time):string {try {if(!$time)return 'Unavailable';return (new DateTimeImmutable((string)$time,new DateTimeZone('Asia/Dhaka')))->setTimezone(new DateTimeZone('Asia/Dhaka'))->format('d M Y, h:i A').' (BDT)';}catch(Throwable $e){return 'Unavailable';}} --- before/public_html/app/order-v41.php +++ after/public_html/app/order-v41.php @@ -0,0 +1,19 @@ +prepare('UPDATE av54_order_workflow SET workflow_status=? WHERE order_id=?')->execute([$next,$id]); // Keep existing storefront tracker in sync with normal workflow updates. if(av54_col($db,'orders','fulfillment_status'))$db->prepare('UPDATE orders SET fulfillment_status=? WHERE id=?')->execute([$next,$id]); --- before/public_html/app/payment-v38.php +++ after/public_html/app/payment-v38.php @@ -77,10 +77,14 @@ if(strtoupper((string)($remote['currency']??''))!=='BDT')return 'unverified'; $pdo->beginTransaction(); try { + // Lock the order first, matching staff cancellation and retry lock order. + $lookup=$pdo->prepare('SELECT order_id FROM av_payment_attempts WHERE transaction_id=? UNION SELECT order_id FROM order_payments WHERE transaction_id=? LIMIT 1');$lookup->execute([$tranId,$tranId]);$oid=$lookup->fetchColumn(); + if(!$oid){$pdo->rollBack();return 'unverified';} + $lock=$pdo->prepare('SELECT status,fulfillment_status FROM orders WHERE id=? FOR UPDATE');$lock->execute([$oid]);$order=$lock->fetch(); $s=$pdo->prepare('SELECT * FROM order_payments WHERE transaction_id=? FOR UPDATE');$s->execute([$tranId]);$pay=$s->fetch();$older=false; if(!$pay){$t=$pdo->prepare('SELECT order_id FROM av_payment_attempts WHERE transaction_id=? FOR UPDATE');$t->execute([$tranId]);$oldOrder=$t->fetchColumn();if($oldOrder){$s=$pdo->prepare('SELECT * FROM order_payments WHERE order_id=? FOR UPDATE');$s->execute([$oldOrder]);$pay=$s->fetch();$older=true;}} if(!$pay || $pay['method']!=='sslcommerz'){$pdo->rollBack();return 'unverified';} - if(abs((float)$remote['amount']-(float)$pay['amount'])>0.009){$pdo->rollBack();return 'unverified';} + if(!isset($remote['amount']) || !is_scalar($remote['amount']) || !preg_match('/^[0-9]+(?:\.[0-9]{1,2})?$/D',(string)$remote['amount']) || abs((float)$remote['amount']-(float)$pay['amount'])>0.009){$pdo->rollBack();return 'unverified';} if($older){ // A previously failed transaction was later charged after a retry began. // Keep all details for staff review; never silently mark the new transaction paid. @@ -90,11 +94,15 @@ } if($pay['status']==='paid'){$pdo->commit();return 'paid';} if(in_array($pay['status'],['cancelled','refunded'],true)){$pdo->rollBack();return 'manual_review';} - $risk=((int)($remote['risk_level']??0)===1)?'review':'paid'; + // A late payment must never reactivate a cancelled order or released stock. + $reservation=$pdo->prepare('SELECT state FROM av_order_reservations WHERE order_id=? FOR UPDATE');$reservation->execute([$pay['order_id']]);$reservationState=$reservation->fetchColumn(); + $risk=(($remote['risk_level']??null) === '0' || ($remote['risk_level']??null) === 0)?'paid':'review'; + if(!$order || in_array($order['status'],['cancelled','refunded'],true) || in_array($order['fulfillment_status'],['cancelled','returned'],true) || !in_array($reservationState,['reserved','consumed'],true))$risk='review'; $s=$pdo->prepare('UPDATE order_payments SET status=?, gateway_val_id=?, gateway_bank_ref=?, gateway_response=?, updated_at=NOW() WHERE id=?'); $pdo->prepare('UPDATE av_payment_attempts SET status=? WHERE transaction_id=?')->execute([$risk,$tranId]); $s->execute([$risk,substr($valId,0,80),substr((string)($remote['bank_tran_id']??''),0,100),json_encode(['status'=>$remote['status'],'risk_level'=>$remote['risk_level']??null],JSON_UNESCAPED_UNICODE),$pay['id']]); - if($risk==='paid'){$s=$pdo->prepare("UPDATE orders SET status='processing' WHERE id=? AND status='pending'");$s->execute([$pay['order_id']]);$pdo->prepare("UPDATE av_order_reservations SET state='consumed' WHERE order_id=? AND state='reserved'")->execute([$pay['order_id']]);} + if($risk==='paid'){// Payment verification does not confirm the staff fulfillment workflow. + $pdo->prepare("UPDATE av_order_reservations SET state='consumed' WHERE order_id=? AND state='reserved'")->execute([$pay['order_id']]);} $pdo->commit();return $risk; }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;} } --- before/public_html/checkout.php +++ after/public_html/checkout.php @@ -4,7 +4,7 @@ if(empty($_SESSION['cart'])){ $previous=av51_recover($pdo); if($previous)av51_redirect($previous[0],$previous[1]); go('cart.php'); } $u=me();$allowCod=av40_payment_enabled($pdo,'cod');$allowOnline=av40_payment_enabled($pdo,'sslcommerz')&&av_online_enabled();$values=$_SESSION['av40_checkout']??[];$errors=[];$message='';$failureRef='';$couriers=$pdo->query("SELECT * FROM av_couriers WHERE active=1 ORDER BY display_order ASC, name ASC")->fetchAll(); if(!isset($_SESSION['av40_checkout_key']))$_SESSION['av40_checkout_key']=bin2hex(random_bytes(32)); -try {[$items,$subtotal,$weight]=av40_items($pdo);}catch(Throwable $e){flash($e->getMessage());go('cart.php');} +try {[$items,$subtotal,$weight]=av40_items($pdo);}catch(Throwable $e){error_log('[AVOLEEN Phase 1] Cart validation: '.get_class($e));flash(av50_checkout_error($e)??'Unable to load your cart. Please try again or contact support.');go('cart.php');} if($_SERVER['REQUEST_METHOD']==='POST'){ check_csrf(); foreach(['recipient','phone','district','upazila','address','courier_other','delivery_option','coupon'] as $k)$values[$k]=trim((string)($_POST[$k]??'')); @@ -30,9 +30,10 @@ $attempt=(string)($_POST['attempt_key']??''); if(!preg_match('/^[a-f0-9]{64}$/',$attempt) || ($attempt!==($_SESSION['av40_checkout_key']??'') && empty($_SESSION['av42_completed_keys'][$attempt])))throw new RuntimeException('This checkout session expired. Refresh the page to continue.'); $s=$pdo->prepare('SELECT order_id FROM av_checkout_attempts WHERE attempt_key=? FOR UPDATE');$s->execute([$attempt]);$existing=$s->fetchColumn(); - if($existing){$pdo->commit();$orderId=(int)$existing;$token=$_SESSION['av40_order_tokens'][$orderId]??'';if(!$token && !$u)throw new RuntimeException('Order already submitted. Use your private tracking link.');av51_store_recent($orderId,$token);av51_redirect($orderId,$token);} + if($existing){$pdo->commit();$orderId=(int)$existing;$token=$_SESSION['av40_order_tokens'][$orderId]??'';if(!av40_order_access($pdo,$orderId,$token))throw new RuntimeException('Order already submitted. Use your private tracking link.');av51_store_recent($orderId,$token);av51_redirect($orderId,$token);} $s=$pdo->prepare('INSERT INTO av_checkout_attempts(attempt_key) VALUES(?)');$s->execute([$attempt]); $stage='cart-lock';[$items,$subtotal,$weight]=av40_items($pdo,true); + if(!$items)throw new RuntimeException('Your cart is empty.'); $discount=av40_coupon($pdo,$values['coupon'],$subtotal); $d=['known'=>true,'base'=>av42_delivery($values['delivery_option']),'extra'=>0.0,'eta'=>'','cod'=>true]; $pending=false; @@ -49,7 +50,7 @@ $s->execute([$u['id']??null,$storedTotal,$values['recipient'],$phone,$fullAddress,$trackingHash,$u['email']??null,$values['district'],$values['upazila'],'',$values['courier_id']>0?$values['courier_id']:null,$values['courier_id']===-1?$values['courier_other']:null,'home',null,null,$pending?'pending_confirmation':'charge_known_assignment_pending',$subtotal,$discount,$d['base'],$d['extra'],$weight,$values['coupon']?:null,$attempt,$values['delivery_option']]); $orderId=(int)$pdo->lastInsertId(); // V41: deterministic public reference from the database-assigned primary key. - $pdo->prepare("UPDATE orders SET public_order_id=CONCAT('avo-',LPAD(id,5,'0')) WHERE id=?")->execute([$orderId]); + $pdo->prepare("UPDATE orders SET public_order_id=CONCAT('avo-',LPAD(id,GREATEST(5,CHAR_LENGTH(id)),'0')) WHERE id=?")->execute([$orderId]); $pdo->prepare("INSERT IGNORE INTO av41_order_events(order_id,event_type,status,source,public_note,event_key) VALUES(?, 'order_status', 'placed', 'system', 'Order received', ?)")->execute([$orderId,'placed:'.$orderId]); $stage='order-items-and-stock';foreach($items as $r){$p=$r['product'];$v=$r['variant'];$s=$pdo->prepare('INSERT INTO order_items(order_id,product_id,variant_id,product_name,variant_label,unit_price,quantity,sku_snapshot,options_snapshot,weight_g_snapshot,image_snapshot) VALUES(?,?,?,?,?,?,?,?,?,?,?)');$s->execute([$orderId,$p['id'],$v['id']??null,$p['name'],$r['label'],$r['price'],$r['qty'],$r['sku'],$r['label'],$r['weight'],($v['image']??$p['image']??null)]); $s=$pdo->prepare($v?'UPDATE variants SET stock=stock-? WHERE id=? AND stock>=?':'UPDATE products SET stock=stock-? WHERE id=? AND stock>=?');$s->execute([$r['qty'],$v['id']??$p['id'],$r['qty']]);if($s->rowCount()!==1)throw new RuntimeException('Stock changed. Please review your cart.'); --- before/public_html/confirm-delivery.php +++ after/public_html/confirm-delivery.php @@ -1,6 +1,7 @@ beginTransaction();$s=$pdo->prepare('SELECT * FROM orders WHERE id=? FOR UPDATE');$s->execute([$id]);$o=$s->fetch();if(!$o || $o['delivery_status']!=='awaiting_customer_acceptance')throw new RuntimeException('Quote is not awaiting acceptance.'); @@ -9,5 +10,5 @@ $pdo->commit();go('order-complete.php?id='.$id.'&token='.$token); }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();$error=$e->getMessage();}} $s=$pdo->prepare('SELECT * FROM orders WHERE id=?');$s->execute([$id]);$o=$s->fetch();header_page('Confirm delivery quote');echo '

Confirm delivery charge

'.($error?'
'.h($error).'
':''); -if($o['delivery_status']!=='awaiting_customer_acceptance')echo '

There is no pending charge to approve.

';else {echo '

Order #'.$id.' · Delivery: '.money($o['delivery_amount']).' · Extra weight: '.money($o['extra_weight_charge']).'

Final total: '.money($o['subtotal']-$o['discount_amount']+$o['delivery_amount']+$o['extra_weight_charge']).'

Accepting this charge confirms your request with Cash on Delivery. Online payment is not yet initiated for requested quotes.

';} +if($o['delivery_status']!=='awaiting_customer_acceptance')echo '

There is no pending charge to approve.

';else {echo '

Order #'.$id.' · Delivery: '.money($o['delivery_amount']).' · Extra weight: '.money($o['extra_weight_charge']).'

Final total: '.money($o['subtotal']-$o['discount_amount']+$o['delivery_amount']+$o['extra_weight_charge']).'

Accepting this charge confirms your request with Cash on Delivery. Online payment is not yet initiated for requested quotes.

';} echo '
';footer_page(); --- before/public_html/order-request.php +++ after/public_html/order-request.php @@ -1,6 +1,7 @@ prepare('SELECT * FROM order_items WHERE order_id=?');$q->execute([$id]);$items=$q->fetchAll(); -header('Cache-Control: no-store, private');header('Content-Type: text/html; charset=UTF-8');echo 'AVOLEEN '.h(av41_public($pdo,$id)).'

AVOLEEN · Order summary

Reference '.h(av41_public($pdo,$id)).' · '.h($o['created_at']).'

Order: '.h(av41_label(av41_customer_order_status($o))).' · Payment: '.h(av41_label($o['payment_status']?:'pending')).'

Recipient: '.h($o['recipient']).'
Phone: '.h($o['phone']).'
Address: '.nl2br(h($o['address'])).'

Preferred courier: '.h($o['preferred_courier']?:($o['courier_other']?:'No preference')).' · '.h(av41_label($o['delivery_method'])).'

';foreach($items as $i)echo '';echo '
Item / selected options / SKUQuantityUnit price
'.h($i['product_name']).'
'.h($i['options_snapshot']?:$i['variant_label']).' · SKU '.h($i['sku_snapshot']?:'—').'
'.(int)$i['quantity'].''.money((float)$i['unit_price']).'
Subtotal'.av41_money($o['subtotal']).'
Discount'.money((float)$o['discount_amount']).'
Delivery'.av41_money($o['delivery_amount']).'
Weight charge'.av41_money($o['extra_weight_charge']).'

Total: '.($o['delivery_status']==='pending_confirmation'?'Pending confirmation':money((float)$o['total'])).'

This summary is not proof of payment.

'; +header('Cache-Control: no-store, private');header('Content-Type: text/html; charset=UTF-8');echo 'AVOLEEN '.h(av41_public($pdo,$id)).'

AVOLEEN · Order summary

Reference '.h(av41_public($pdo,$id)).' · '.h($o['created_at']).'

Order: '.h(av41_label(av41_customer_order_status($o))).' · Payment: '.h(av41_label($o['payment_status']?:'pending')).'

Recipient: '.h($o['recipient']).'
Phone: '.h($o['phone']).'
Address: '.nl2br(h($o['address'])).'

Preferred courier: '.h($o['preferred_courier']?:($o['courier_other']?:'No preference')).' · '.h(av41_label($o['delivery_method'])).'

';foreach($items as $i)echo '';echo '
Item / selected options / SKUQuantityUnit price
'.h($i['product_name']).'
'.h($i['options_snapshot']?:$i['variant_label']).' · SKU '.h($i['sku_snapshot']?:'—').'
'.(int)$i['quantity'].''.money((float)$i['unit_price']).'
Subtotal'.av41_money($o['subtotal']).'
Discount'.money((float)$o['discount_amount']).'
Delivery'.av41_money($o['delivery_amount']).'
Weight charge'.av41_money($o['extra_weight_charge']).'

Total: '.($o['delivery_status']==='pending_confirmation'?'Pending confirmation':money((float)$o['total'])).'

This summary is not proof of payment.

'; --- before/public_html/payment/retry.php +++ after/public_html/payment/retry.php @@ -1,6 +1,7 @@ beginTransaction(); + $lock=$pdo->prepare('SELECT status,fulfillment_status FROM orders WHERE id=? FOR UPDATE');$lock->execute([$id]);$lockedOrder=$lock->fetch(); + $lock=$pdo->prepare('SELECT state FROM av_order_reservations WHERE order_id=? FOR UPDATE');$lock->execute([$id]);$lockedReservation=$lock->fetchColumn(); + if(!$lockedOrder || $lockedOrder['status']==='cancelled' || in_array($lockedOrder['fulfillment_status'],['cancelled','returned','delivered'],true) || $lockedReservation!=='reserved')throw new RuntimeException('This order is not eligible for payment retry.'); $s=$pdo->prepare('SELECT p.*,o.total,o.delivery_status FROM order_payments p JOIN orders o ON o.id=p.order_id WHERE p.order_id=? FOR UPDATE');$s->execute([$id]);$p=$s->fetch(); if(!$p||$p['status']==='paid'||$p['transaction_id']!==$order['transaction_id']||$p['method']!=='sslcommerz'||$p['delivery_status']!=='charge_known_assignment_pending'&&$p['delivery_status']!=='courier_assigned')throw new RuntimeException('Payment state changed. Reload order.'); $pdo->prepare("UPDATE av_payment_attempts SET status='failed' WHERE transaction_id=?")->execute([$order['transaction_id']]); $s=$pdo->prepare("UPDATE order_payments SET transaction_id=?,status='initiating',amount=? WHERE order_id=?");$s->execute([$newTran,$p['total'],$id]); $pdo->prepare("INSERT INTO av_payment_attempts(order_id,transaction_id,status) VALUES(?,?,'initiating')")->execute([$id,$newTran]);$pdo->commit(); - try{$gateway=av_create_gateway_session(['order_id'=>$id,'transaction_id'=>$newTran,'amount'=>(float)$order['total']],['name'=>$order['recipient'],'email'=>$order['contact_email'],'phone'=>$order['phone'],'address'=>$order['address']]); + try{$gateway=av_create_gateway_session(['order_id'=>$id,'transaction_id'=>$newTran,'amount'=>(float)$p['total']],['name'=>$order['recipient'],'email'=>filter_var($order['contact_email'],FILTER_VALIDATE_EMAIL)?$order['contact_email']:'no-reply@avoleen.com','phone'=>$order['phone'],'address'=>$order['address']]); $pdo->prepare("UPDATE order_payments SET status='pending' WHERE order_id=? AND status='initiating'")->execute([$id]);header('Location: '.$gateway,true,303);exit; }catch(Throwable $e){error_log('[AVOLEEN V40] Retry initiation failed for order '.$id.': '.$e->getMessage());$pdo->prepare("UPDATE order_payments SET status='review' WHERE order_id=? AND status='initiating'")->execute([$id]);throw new RuntimeException('Payment could not be started; contact support with your order number.');} - }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();$error=$e->getMessage();}} -header_page('Retry payment');echo '

Retry payment · Order #'.$id.'

Before retrying, AVOLEEN will verify that the previous gateway transaction was definitely failed or cancelled. Pending or uncertain payments cannot be retried automatically.

Current payment status: '.h($order['payment_status']).'

'.($error?'
'.h($error).'
':'').'

Back to order

';footer_page(); + }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();error_log('[AVOLEEN Phase 1] Retry: '.get_class($e));$error=$e instanceof PDOException?'Payment is temporarily unavailable. Contact support before retrying.':$e->getMessage();}} +header_page('Retry payment');echo '

Retry payment · Order #'.$id.'

Before retrying, AVOLEEN will verify that the previous gateway transaction was definitely failed or cancelled. Pending or uncertain payments cannot be retried automatically.

Current payment status: '.h($order['payment_status']).'

'.($error?'
'.h($error).'
':'').'

Back to order

';footer_page(); --- before/public_html/payment/return.php +++ after/public_html/payment/return.php @@ -1,6 +1,7 @@ '; echo '
AVOLEEN ORDER TRACKING

Track your order

'.h($publicId).' '.h($state).'

'.h(av53_date($o['created_at']??null)).'

'; echo '

Order Progress

'; - $steps=['Order Received','Confirmed','Processing','Shipped','Out for Delivery','Delivered']; + $steps=['Order Received','Confirmed','Processing','Ready to Ship','Shipped','Out for Delivery','Delivered']; $idx=array_search($state,$steps,true);if($idx===false)$idx=0; foreach($steps as $n=>$label)echo ''.($n<=$idx?'✓ ':'').h($label).''; echo '
Shipment Tracking

Preferred Courier: '.h($preferred).'

';