
本文详解如何通过代码在 woocommerce 中 programmatically 创建免费商品订单,并解决因状态名称大小写错误导致的“payment pending”问题,确保零价格订单自动标记为已完成。
本文详解如何通过代码在 woocommerce 中 programmatically 创建免费商品订单,并解决因状态名称大小写错误导致的“payment pending”问题,确保零价格订单自动标记为已完成。
在 WooCommerce 中以编程方式创建订单(尤其是零价格免费产品)时,一个常见却极易被忽略的陷阱是:订单状态字符串必须严格使用小写英文标识符。你提供的代码中调用了 $order->update_status("Completed", ...),其中 "Completed" 首字母大写——这会导致 WooCommerce 无法识别该状态,从而默认回退为 pending(即后台显示为 “Payment pending”),即使商品价格为 0 且无需支付。
✅ 正确的状态值(全部小写)
WooCommerce 内置订单状态均为小写,合法值包括:
- pending
- failed
- on-hold
- processing
- completed ✅(用于已履约的免费订单)
- cancelled
- refunded
⚠️ 注意:"Completed"、"COMPLETED" 或 "complete" 均无效,仅 completed 可被正确解析。
✅ 修复后的完整示例代码(含健壮性增强)
add_action('wpcf7_before_send_mail', 'CF7_pre_send');
function CF7_pre_send($cf7) {
// 防止非 POST 请求或空数据执行
if (!isset($_POST['firstname']) || empty($_POST['firstname'])) {
return;
}
// 获取表单中选中的产品 ID(假设下拉框 value 是 product ID)
$product_id = absint($_POST['products-54'] ?? 0);
if (!$product_id || !wc_get_product($product_id)) {
error_log('Invalid or missing product ID in CF7 submission.');
return;
}
// 构建地址数组(注意:移除非法键如 "product",避免污染 billing/shipping 地址)
$address = array(
'first_name' => sanitize_text_field($_POST['firstname'] ?? ''),
'last_name' => sanitize_text_field($_POST['LastName'] ?? ''),
'country' => sanitize_text_field($_POST['country_auto-565'] ?? 'US'),
'state' => sanitize_text_field($_POST['state_auto-625'] ?? ''),
'city' => sanitize_text_field($_POST['city_auto-896'] ?? ''),
'postcode' => sanitize_text_field($_POST['number-594'] ?? ''),
'address_1' => sanitize_text_field($_POST['Address'] ?? ''),
'address_2' => sanitize_text_field($_POST['Address'] ?? ''), // 注意:此处可能应为不同字段,需按实际表单调整
'phone' => sanitize_text_field($_POST['phonetext-523'] ?? ''),
'email' => sanitize_email($_POST['email-557'] ?? ''),
);
// 创建订单
$order = wc_create_order();
// 添加商品(自动处理零价逻辑)
$product = wc_get_product($product_id);
if ($product) {
$order->add_product($product, 1);
} else {
error_log("Product ID {$product_id} not found when creating order.");
return;
}
// 设置收货与配送地址(WooCommerce 会自动复制 billing → shipping 若未单独设置)
$order->set_address($address, 'billing');
$order->set_address($address, 'shipping');
// 关键:强制将订单总金额设为 0(即使商品价格为 0,仍建议显式确认)
$order->set_total(0, 'total');
$order->set_subtotal(0);
$order->set_currency(get_woocommerce_currency());
// 计算总计(触发税费、运费等钩子,但零价下通常无影响)
$order->calculate_totals(false); // false 表示不重新计算税费/运费(可选优化)
// ✅ 正确设置状态:全部小写!
$order->update_status('completed', 'Free product order imported via CF7 form.', true);
// (可选)记录日志便于调试
error_log("Free order #{$order->get_id()} created successfully for product {$product_id}.");
}
? 关于表单产品下拉框的关键说明
你提到“无法获取选中产品的 ID”,这取决于 Contact Form 7 下拉字段的配置:
-
✅ 推荐做法:在 CF7 表单编辑器中,将下拉选项的 value 直接设为 WooCommerce 商品 ID,例如:
[select products-54 "Select a product" "Fitness Guide (ID: 20604)|20604" "Meal Plan (ID: 20605)|20605"]
这样 $_POST['products-54'] 就直接返回 20604 等整型 ID,无需额外映射。
❌ 避免使用中文或描述性文本作为 value(如 "Fitness Guide"),否则需维护 ID 映射数组,增加出错风险。
? 注意事项与最佳实践
- 安全过滤:始终对 $_POST 数据使用 sanitize_* 函数(如 sanitize_text_field, sanitize_email, absint),防止 XSS 或注入。
- 权限与钩子时机:wpcf7_before_send_mail 在邮件发送前触发,适合此场景;若需更高可靠性,可改用 wpcf7_mail_sent(但需注意此时邮件已发出)。
- 零价订单的支付网关:WooCommerce 默认对 0.00 订单跳过支付流程,直接进入 completed(前提是状态设置正确)。无需启用“Cash on Delivery”等虚拟网关。
- 调试技巧:启用 WordPress 调试日志(WP_DEBUG_LOG),用 error_log() 输出关键变量,比 file_put_contents() 更符合 WordPress 规范。
遵循以上修正与规范,你的免费产品表单即可稳定生成状态为 completed 的订单,彻底告别 “Payment pending” 困扰。










