Files
wordpress-theme-aksrt/inc/classes/shop/class-aksrt-shop-product-service.php
T
kate522 c93b303453 feat(payment): finish Zibll key contract and wire checkout gateways
Drop dual-read payment runtime fallbacks, align PHP schema to Vue keys,
read gateway credentials only from aksrt_settings with enable toggles,
expose configured adapters at checkout, fix credits pay signatures, and
apply order expiry settings. Wire orphan order/card-pass/withdrawal
schema pages; delete unused Payment* wrappers.
2026-07-11 17:42:35 +08:00

367 lines
13 KiB
PHP

<?php
/**
* AKSRT Shop Product Service
*
* Variant and product-meta helpers extracted from AKSRT_Shop.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Shop_Product_Service {
private AKSRT_Shop $shop;
public function __construct(AKSRT_Shop $shop) {
$this->shop = $shop;
}
/* ============================================================
* Product meta helpers
* ============================================================ */
public function get_term_options(string $taxonomy): array {
$terms = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
]);
if (is_wp_error($terms) || empty($terms)) {
return [];
}
return array_map(static function ($term) {
return [
'id' => (int) $term->term_id,
'name' => (string) $term->name,
];
}, $terms);
}
public function get_product_gallery(int $product_id): array {
$gallery = get_post_meta($product_id, '_aksrt_product_gallery', true);
if (!is_array($gallery)) {
return [];
}
return array_values(array_filter(array_map('esc_url_raw', $gallery)));
}
public function get_product_shipping_profile(int $product_id): array {
return [
'mode' => sanitize_key((string) get_post_meta($product_id, '_aksrt_shipping_mode', true)) ?: 'express',
'fee' => (float) get_post_meta($product_id, '_aksrt_shipping_fee', true),
'delivery_note' => (string) get_post_meta($product_id, '_aksrt_delivery_note', true),
];
}
public function get_product_after_sale_profile(int $product_id): array {
return [
'policy' => (string) get_post_meta($product_id, '_aksrt_after_sale_policy', true),
'support_refund' => get_post_meta($product_id, '_aksrt_support_refund', true) !== '0',
];
}
public function get_product_marketing_profile(int $product_id): array {
return [
'headline' => (string) get_post_meta($product_id, '_aksrt_marketing_headline', true),
'shipping_tip' => (string) get_post_meta($product_id, '_aksrt_shipping_tip', true),
'service_tip' => (string) get_post_meta($product_id, '_aksrt_service_tip', true),
];
}
public function get_payment_method_options(): array {
$methods = [];
if (class_exists('AKSRT_Payment_Gateway')) {
foreach (AKSRT_Payment_Gateway::get_instance()->get_all() as $id => $gateway) {
if (!method_exists($gateway, 'is_configured') || !$gateway->is_configured()) {
continue;
}
$methods[$id] = [
'value' => $id,
'label' => method_exists($gateway, 'get_name') ? (string) $gateway->get_name() : $id,
'description' => $this->gateway_method_description((string) $id),
];
}
}
if (class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled()) {
$methods['points'] = [
'value' => 'points',
'label' => __('积分兑换', 'aksrt'),
'description' => __('使用积分购买积分商品。', 'aksrt'),
];
}
$manual = true;
if (class_exists('AKSRT_Settings')) {
$gw = AKSRT_Settings::get_payment_gateway_settings();
$manual = !empty($gw['manual_review']) || ($gw['mode'] ?? '') === 'manual_review';
}
if ($manual) {
$methods['manual_review'] = [
'value' => 'manual_review',
'label' => __('人工确认', 'aksrt'),
'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'),
];
}
if (empty($methods)) {
$methods['manual_review'] = [
'value' => 'manual_review',
'label' => __('人工确认', 'aksrt'),
'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'),
];
}
return $methods;
}
private function gateway_method_description(string $id): string {
switch ($id) {
case 'balance':
return __('即时完成支付并写回订单状态。', 'aksrt');
case 'alipay':
return __('跳转支付宝完成支付。', 'aksrt');
case 'wechatpay':
return __('微信扫码或跳转完成支付。', 'aksrt');
case 'paypal':
return __('跳转 PayPal 完成支付。', 'aksrt');
case 'stripe':
return __('跳转 Stripe Checkout 完成支付。', 'aksrt');
default:
return '';
}
}
public function get_shipping_method_options(): array {
return [
'express' => [
'value' => 'express',
'label' => __('快递配送', 'aksrt'),
'description' => __('标准快递,支持物流跟踪。', 'aksrt'),
'fee' => 12.0,
],
'ems' => [
'value' => 'ems',
'label' => __('EMS', 'aksrt'),
'description' => __('偏远地区可选。', 'aksrt'),
'fee' => 18.0,
],
'download' => [
'value' => 'download',
'label' => __('自动发货', 'aksrt'),
'description' => __('适用于数字商品。', 'aksrt'),
'fee' => 0.0,
],
];
}
public function get_limit_buy_config(int $product_id): array {
$raw = get_post_meta($product_id, '_aksrt_limit_buy_config', true);
if (!is_array($raw)) {
$legacy = (int) get_post_meta($product_id, '_aksrt_limit_buy', true);
return ['default' => $legacy > 0 ? $legacy : -1, 'desc' => ''];
}
return [
'default' => (int) ($raw['default'] ?? -1),
'vip' => (int) ($raw['vip'] ?? -1),
'vip2' => (int) ($raw['vip2'] ?? -1),
'auth' => (int) ($raw['auth'] ?? -1),
'desc' => sanitize_text_field((string) ($raw['desc'] ?? '')),
];
}
public function get_product_services(int $product_id): array {
$raw = get_post_meta($product_id, '_aksrt_services', true);
if (!is_array($raw)) return [];
return array_values(array_filter(array_map(static function ($s) {
$name = sanitize_text_field((string) ($s['name'] ?? ''));
if ($name === '') return null;
return [
'name' => $name,
'desc' => sanitize_text_field((string) ($s['desc'] ?? '')),
'icon' => esc_url_raw((string) ($s['icon'] ?? '')),
];
}, $raw)));
}
public function get_product_params(int $product_id): array {
$raw = get_post_meta($product_id, '_aksrt_product_params', true);
if (!is_array($raw)) return [];
return array_values(array_filter(array_map(static function ($p) {
$name = sanitize_text_field((string) ($p['name'] ?? ''));
if ($name === '') return null;
return [
'name' => $name,
'value' => sanitize_text_field((string) ($p['value'] ?? '')),
];
}, $raw)));
}
/**
* Resolve product main image with 3-tier fallback:
* 1. _aksrt_product_image (custom main image)
* 2. WordPress featured image (post thumbnail)
* 3. First gallery image from _aksrt_product_gallery
*/
public function resolve_main_image(int $product_id): string {
$custom = (string) get_post_meta($product_id, '_aksrt_product_image', true);
if ($custom !== '') {
return esc_url_raw($custom);
}
$thumb = get_the_post_thumbnail_url($product_id, 'large');
if ($thumb) {
return (string) $thumb;
}
$gallery = get_post_meta($product_id, '_aksrt_product_gallery', true);
if (is_array($gallery) && !empty($gallery)) {
$first = reset($gallery);
if ($first) {
return esc_url_raw((string) $first);
}
}
return '';
}
public function build_product_card_view(int $product_id): array {
$post = get_post($product_id);
if (!$post || $post->post_type !== 'aksrt_product') {
return [];
}
$stock_raw = (int) get_post_meta($product_id, '_aksrt_stock', true);
$is_unlimited = $stock_raw <= -1;
return [
'id' => $product_id,
'title' => (string) get_the_title($product_id),
'excerpt' => (string) get_the_excerpt($product_id),
'price' => (float) get_post_meta($product_id, '_aksrt_price', true),
'image' => $this->resolve_main_image($product_id),
'stock' => $stock_raw,
'is_unlimited' => $is_unlimited,
'sales' => (int) get_post_meta($product_id, '_aksrt_sales', true),
'status' => (string) get_post_status($product_id),
'created' => get_the_date('Y-m-d H:i', $product_id),
'permalink' => get_permalink($product_id) ?: '',
];
}
/* ============================================================
* Product Variants / SKU
* ============================================================ */
/**
* Get variants table name.
*/
public function variants_table(): string {
global $wpdb;
return $wpdb->prefix . 'aksrt_product_variant';
}
/**
* Get all variants for a product.
*/
public function get_variants(int $product_id): array {
global $wpdb;
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$this->variants_table()} WHERE product_id = %d ORDER BY sort_order ASC, id ASC",
$product_id
), ARRAY_A);
if (!$rows) return [];
return array_map(function ($row) {
$row['id'] = (int) $row['id'];
$row['product_id'] = (int) $row['product_id'];
$row['price'] = (float) $row['price'];
$row['stock'] = (int) $row['stock'];
$row['sort_order'] = (int) $row['sort_order'];
$row['attributes'] = json_decode($row['attributes'] ?? '[]', true) ?: [];
return $row;
}, $rows);
}
/**
* Get a single variant by ID.
*/
public function get_variant(int $variant_id): ?array {
global $wpdb;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->variants_table()} WHERE id = %d",
$variant_id
), ARRAY_A);
if (!$row) return null;
$row['id'] = (int) $row['id'];
$row['product_id'] = (int) $row['product_id'];
$row['price'] = (float) $row['price'];
$row['stock'] = (int) $row['stock'];
$row['attributes'] = json_decode($row['attributes'] ?? '[]', true) ?: [];
return $row;
}
/**
* Save variants for a product (replace all).
*/
public function save_variants(int $product_id, array $variants): array {
global $wpdb;
$table = $this->variants_table();
// Delete existing variants
$wpdb->delete($table, ['product_id' => $product_id]);
$saved = [];
foreach ($variants as $index => $v) {
$data = [
'product_id' => $product_id,
'sku' => sanitize_text_field((string) ($v['sku'] ?? '')),
'attributes' => wp_json_encode($v['attributes'] ?? []),
'price' => max(0, (float) ($v['price'] ?? 0)),
'stock' => max(0, (int) ($v['stock'] ?? 0)),
'image' => esc_url_raw((string) ($v['image'] ?? '')),
'sort_order' => $index,
'created_at' => current_time('mysql', true),
];
$wpdb->insert($table, $data);
$saved[] = array_merge($data, ['id' => (int) $wpdb->insert_id]);
}
// Update product meta flag
update_post_meta($product_id, '_aksrt_has_variants', count($saved) > 0 ? '1' : '0');
return $saved;
}
/**
* Delete a single variant.
*/
public function delete_variant(int $variant_id): bool {
global $wpdb;
$variant = $this->get_variant($variant_id);
if (!$variant) return false;
$result = $wpdb->delete($this->variants_table(), ['id' => $variant_id]);
if ($result) {
// Update product meta flag
$remaining = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$this->variants_table()} WHERE product_id = %d",
$variant['product_id']
));
update_post_meta($variant['product_id'], '_aksrt_has_variants', $remaining > 0 ? '1' : '0');
}
return (bool) $result;
}
/**
* Check if a product has variants.
*/
public function has_variants(int $product_id): bool {
return get_post_meta($product_id, '_aksrt_has_variants', true) === '1';
}
}