<?php
defined('ABSPATH') || exit;
/**
* Robust partial checkout to regular /checkout/ with durable cart snapshot
* - Snapshot full cart before virtualizing selection for checkout
* - Do not remove anything until order is created
* - On success (thank-you), rebuild cart as (snapshot - purchased)
* - On cancel/back (visit cart), restore snapshot
* - Guest resilience: localStorage + rehydrate AJAX
*/
/* -------------------------------------------------
* Helpers
* ------------------------------------------------- */
function pc_get_cart_uid() {
if (is_user_logged_in()) {
return 'user_' . get_current_user_id();
}
if (empty($_COOKIE['pc_cart_uid'])) {
$token = wp_generate_uuid4();
setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false);
$_COOKIE['pc_cart_uid'] = $token;
}
return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid']));
}
function pc_build_item_key($product_id, $variation_id = 0) {
return (int)$product_id . '|' . (int)$variation_id;
}
function pc_snapshot_current_cart() {
if (!WC()->cart) wc_load_cart();
$items = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
$pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0;
$vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0;
$qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0;
$var = isset($ci['variation']) && is_array($ci['variation']) ? $ci['variation'] : array();
if ($pid && $qty > 0) {
$items[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => array_map('wc_clean', $var),
'quantity' => $qty,
);
}
}
return $items;
}
function pc_restore_cart_from_items($items) {
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
foreach ((array)$items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
}
function pc_transient_key($token) {
return 'pc_partial_payload_' . sanitize_key($token);
}
/* -------------------------------------------------
* AJAX: Local rehydrate when Woo cart is empty
* ------------------------------------------------- */
add_action('wp_ajax_pc_rehydrate_cart', 'pc_rehydrate_cart');
add_action('wp_ajax_nopriv_pc_rehydrate_cart', 'pc_rehydrate_cart');
function pc_rehydrate_cart() {
check_ajax_referer('woocommerce-cart', 'security');
$raw = isset($_POST['items']) ? wp_unslash($_POST['items']) : '';
$items = is_string($raw) ? json_decode($raw, true) : (array)$raw;
if (!is_array($items)) {
wp_send_json_error(array('message' => 'Invalid items.'), 400);
}
if (!WC()->cart) wc_load_cart();
if (!WC()->cart->is_empty()) {
wp_send_json_success(array('message' => 'Cart not empty.'));
}
foreach ($items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
wp_send_json_success(array('rehydrated' => true));
}
/* -------------------------------------------------
* AJAX: Update qty (per-row; no page reload)
* ------------------------------------------------- */
add_action('wp_ajax_update_cart_item_qty', 'pc_update_cart_item_qty');
add_action('wp_ajax_nopriv_update_cart_item_qty', 'pc_update_cart_item_qty');
function pc_update_cart_item_qty() {
check_ajax_referer('woocommerce-cart', 'security');
$key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : '';
$qty = isset($_POST['qty']) ? wc_stock_amount($_POST['qty']) : null;
if (!$key || $qty === null) {
wp_send_json_error(array('message' => 'Missing params.'), 400);
}
if (!WC()->cart) wc_load_cart();
if ($qty <= 0) {
$removed = WC()->cart->remove_cart_item($key);
WC()->cart->calculate_totals();
wp_send_json_success(array('removed' => (bool)$removed));
} else {
$set = WC()->cart->set_quantity($key, $qty, true);
WC()->cart->calculate_totals();
$cart_item = WC()->cart->get_cart_item($key);
if (!$cart_item) {
wp_send_json_error(array('message' => 'Cart item not found after update.'), 404);
}
$_product = $cart_item['data'];
$subtotal_html = apply_filters(
'woocommerce_cart_item_subtotal',
WC()->cart->get_product_subtotal($_product, $cart_item['quantity']),
$cart_item,
$key
);
// Use line_total + line_tax (after totals) for checkbox data-price
$line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']);
wp_send_json_success(array(
'subtotal_html' => $subtotal_html,
'line_total_incl_tax' => $line_total_incl_tax,
'removed' => false,
));
}
}
/* -------------------------------------------------
* AJAX: Remove selected
* ------------------------------------------------- */
add_action('wp_ajax_remove_selected_cart_items', 'pc_remove_selected_cart_items');
add_action('wp_ajax_nopriv_remove_selected_cart_items', 'pc_remove_selected_cart_items');
function pc_remove_selected_cart_items() {
check_ajax_referer('woocommerce-cart', 'security');
$keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (!WC()->cart) wc_load_cart();
foreach ($keys as $k) {
$k = wc_clean(wp_unslash($k));
WC()->cart->remove_cart_item($k);
}
WC()->cart->calculate_totals();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: Empty cart
* ------------------------------------------------- */
add_action('wp_ajax_empty_cart', 'pc_empty_cart');
add_action('wp_ajax_nopriv_empty_cart', 'pc_empty_cart');
function pc_empty_cart() {
check_ajax_referer('woocommerce-cart', 'security');
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: Apply coupon
* ------------------------------------------------- */
add_action('wp_ajax_apply_coupon', 'pc_apply_coupon');
add_action('wp_ajax_nopriv_apply_coupon', 'pc_apply_coupon');
function pc_apply_coupon() {
check_ajax_referer('woocommerce-cart', 'security');
$code = isset($_POST['coupon_code']) ? wc_format_coupon_code(wp_unslash($_POST['coupon_code'])) : '';
if (!$code) {
wp_send_json_error(array('message' => __('请输入优惠券代码', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
$applied = WC()->cart->apply_coupon($code);
WC()->cart->calculate_totals();
if (is_wp_error($applied)) {
wp_send_json_error(array('message' => $applied->get_error_message()), 400);
}
if (!$applied) {
wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400);
}
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: Start partial checkout to regular checkout page
* ------------------------------------------------- */
add_action('wp_ajax_create_direct_order', 'pc_create_direct_order');
add_action('wp_ajax_nopriv_create_direct_order', 'pc_create_direct_order');
function pc_create_direct_order() {
check_ajax_referer('woocommerce-cart', 'security');
$selected_keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (empty($selected_keys)) {
wp_send_json_error(array('message' => __('请选择要结算的商品', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
// Snapshot full cart
$snapshot = pc_snapshot_current_cart();
// Build selected items from current cart based on cart_item_key list
$selected = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
if (!in_array($ci_key, $selected_keys, true)) {
continue;
}
$pid = (int)$ci['product_id'];
$vid = (int)$ci['variation_id'];
$qty = wc_stock_amount($ci['quantity']);
$var = isset($ci['variation']) && is_array($ci['variation']) ? array_map('wc_clean', $ci['variation']) : array();
if ($pid && $qty > 0) {
$selected[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => $var,
'quantity' => $qty,
);
}
}
if (empty($selected)) {
wp_send_json_error(array('message' => __('没有可结算的商品', 'woocommerce')), 400);
}
$token = wp_generate_uuid4();
$payload = array(
'uid' => pc_get_cart_uid(),
'snapshot' => $snapshot,
'selected' => $selected,
'created' => time(),
);
set_transient(pc_transient_key($token), $payload, 2 * DAY_IN_SECONDS);
// Put token in session (used across checkout AJAX calls)
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
$checkout_url = add_query_arg('pc_token', rawurlencode($token), wc_get_checkout_url());
wp_send_json_success(array('checkout_url' => $checkout_url));
}
/* -------------------------------------------------
* Virtualize cart on checkout for token and rebuild after purchase
* ------------------------------------------------- */
// Entering checkout with token: virtualize cart to selected items
add_action('woocommerce_before_checkout_form', function() {
if (!isset($_GET['pc_token'])) return;
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
if (!WC()->cart) wc_load_cart();
// Virtualize to selected items only
pc_restore_cart_from_items($payload['selected']);
// Persist token in session for all subsequent checkout AJAX calls
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
}, 1);
// Safety: just-in-time re-virtualization before order processing
add_action('woocommerce_before_checkout_process', function() {
if (!method_exists(WC()->session, 'get')) return;
$token = WC()->session->get('pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
// Ensure cart still equals selected set
pc_restore_cart_from_items($payload['selected']);
}, 1);
// Tag order with token
add_action('woocommerce_checkout_create_order', function($order) {
$token = null;
if (isset($_GET['pc_token'])) {
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
} elseif (method_exists(WC()->session, 'get')) {
$token = WC()->session->get('pc_partial_token');
}
if ($token) {
$order->update_meta_data('_pc_partial_token', $token);
$order->update_meta_data('_pc_cart_snapshot', $token);
}
}, 10, 1);
// Fixed: Only remove purchased items from cart
add_action('woocommerce_thankyou', function($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$token = $order->get_meta('_pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['snapshot'])) {
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
return;
}
// 1. Restore FULL snapshot (all items)
pc_restore_cart_from_items($payload['snapshot']);
WC()->cart->calculate_totals();
// 2. Remove only the purchased (selected) items
foreach ($payload['selected'] as $selected_item) {
$cart_item_key = pc_find_cart_item($selected_item['product_id'], $selected_item['variation_id']);
if ($cart_item_key) {
WC()->cart->remove_cart_item($cart_item_key);
}
}
// 3. Update cart totals
WC()->cart->calculate_totals();
// Cleanup token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
}, 20);
// Helper: Find cart item by product and variation ID
function pc_find_cart_item($product_id, $variation_id = 0) {
if (!WC()->cart) wc_load_cart();
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
$cart_pid = isset($cart_item['product_id']) ? (int)$cart_item['product_id'] : 0;
$cart_vid = isset($cart_item['variation_id']) ? (int)$cart_item['variation_id'] : 0;
if ($cart_pid == $product_id && $cart_vid == $variation_id) {
return $cart_item_key;
}
}
return false;
}
// Visiting cart with active token: restore full snapshot (cancel/back)
add_action('woocommerce_before_cart', function() {
if (!method_exists(WC()->session, 'get')) return;
$token = WC()->session->get('pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['snapshot'])) return;
// Restore full snapshot so cart page always shows everything
pc_restore_cart_from_items($payload['snapshot']);
}, 1);
/* -------------------------------------------------
* Keep cart count accurate during checkout process
* ------------------------------------------------- */
add_filter('woocommerce_cart_contents_count', function($count) {
// Check if partial token exists
if (!method_exists(WC()->session, 'get')) return $count;
$token = WC()->session->get('pc_partial_token');
if ($token) {
$payload = get_transient(p
You generated the functions.php halfway and havent generate code for cart.ophp yet. Please coninue from
if ($token) {
$payload = get_transient(p
最新发布