get_var($wpdb->prepare( "SELECT id FROM {$this->table()} WHERE code = %s", $code )); if ($exists) return 0; $type = sanitize_key((string) ($data['type'] ?? self::TYPE_FIXED)); if (!in_array($type, [self::TYPE_PERCENT, self::TYPE_FIXED], true)) { $type = self::TYPE_FIXED; } $wpdb->insert($this->table(), [ 'code' => $code, 'type' => $type, 'value' => (float) ($data['value'] ?? 0), 'min_amount' => (float) ($data['min_amount'] ?? 0), 'usage_limit' => (int) ($data['usage_limit'] ?? 0), 'used_count' => 0, 'expires_at' => sanitize_text_field((string) ($data['expires_at'] ?? '')), 'per_user_limit' => (int) ($data['per_user_limit'] ?? 0), 'product_scope' => wp_json_encode($data['product_scope'] ?? [], JSON_UNESCAPED_UNICODE), 'max_discount' => (float) ($data['max_discount'] ?? 0), 'status' => 'active', 'created_at' => current_time('mysql'), ], ['%s', '%s', '%f', '%f', '%d', '%d', '%s', '%d', '%s', '%f', '%s', '%s']); return (int) $wpdb->insert_id; } /** * 获取优惠券列表 */ public function get_list(array $args = []): array { global $wpdb; $where = []; $params = []; $status = sanitize_key((string) ($args['status'] ?? '')); if ($status !== '') { $where[] = 'status = %s'; $params[] = $status; } $search = sanitize_text_field((string) ($args['search'] ?? '')); if ($search !== '') { $where[] = 'code LIKE %s'; $params[] = '%' . $wpdb->esc_like($search) . '%'; } $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); $offset = max(0, (int) ($args['offset'] ?? 0)); $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $count_sql = "SELECT COUNT(*) FROM {$this->table()} {$where_sql}"; if (!empty($params)) { $total = (int) $wpdb->get_var($wpdb->prepare($count_sql, $params)); } else { $total = (int) $wpdb->get_var($count_sql); } $sql = "SELECT * FROM {$this->table()} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d"; $params[] = $limit; $params[] = $offset; $rows = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A); return [ 'items' => array_map([$this, 'normalize_row'], (array) $rows), 'total' => $total, 'page' => max(1, (int) ceil($offset / $limit) + 1), 'total_pages' => max(1, (int) ceil($total / $limit)), ]; } /** * 验证优惠券是否可用 * * @return array{valid: bool, coupon?: array, message?: string} */ public function validate(string $code, float $order_amount = 0, array $args = []): array { global $wpdb; $code = sanitize_text_field($code); if ($code === '') { return ['valid' => false, 'message' => '请输入优惠码']; } $coupon = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$this->table()} WHERE code = %s", $code ), ARRAY_A); if (!$coupon) { return ['valid' => false, 'message' => '优惠码不存在']; } $coupon = $this->normalize_row($coupon); if ($coupon['status'] !== 'active') { return ['valid' => false, 'message' => '优惠码已失效']; } if (!empty($coupon['expires_at']) && strtotime($coupon['expires_at']) < time()) { return ['valid' => false, 'message' => '优惠码已过期']; } if ($coupon['usage_limit'] > 0 && $coupon['used_count'] >= $coupon['usage_limit']) { return ['valid' => false, 'message' => '优惠码已用完']; } if ($coupon['min_amount'] > 0 && $order_amount < $coupon['min_amount']) { return ['valid' => false, 'message' => '订单金额不满足最低消费要求']; } // Per-user limit check $user_id = (int) ($args['user_id'] ?? 0); if ($coupon['per_user_limit'] > 0 && $user_id > 0) { $user_used = $this->get_user_usage_count($coupon['id'], $user_id); if ($user_used >= $coupon['per_user_limit']) { return ['valid' => false, 'message' => '您已使用该优惠码达到上限']; } } // Product / content scope check. // Supports shop product cats and content post cats. if (!empty($coupon['product_scope']) && !empty($args['product_ids'])) { $scope = is_array($coupon['product_scope']) ? $coupon['product_scope'] : json_decode($coupon['product_scope'], true); if (is_array($scope) && !empty($scope)) { $scope_type = sanitize_key((string) ($scope['type'] ?? '')); $scope_ids = array_map('intval', (array) ($scope['ids'] ?? [])); $product_ids = array_map('intval', (array) $args['product_ids']); $taxonomies = array_values(array_filter(array_map('sanitize_key', (array) ($scope['taxonomies'] ?? [])))); if ($taxonomies === []) { // content context prefers category; shop admin may pass aksrt_product_cat. $context = sanitize_key((string) ($args['context'] ?? '')); $taxonomies = ($context === 'content') ? ['category'] : ['aksrt_product_cat', 'category']; } if ($scope_type === 'include' && empty(array_intersect($product_ids, $scope_ids))) { return ['valid' => false, 'message' => __('该优惠码不适用于当前商品', 'aksrt')]; } if ($scope_type === 'exclude' && !empty(array_intersect($product_ids, $scope_ids))) { return ['valid' => false, 'message' => __('该优惠码不适用于当前商品', 'aksrt')]; } if (in_array($scope_type, ['category', 'content_category', 'post_category'], true)) { $matched = false; foreach ($product_ids as $pid) { foreach ($taxonomies as $tax) { $cats = wp_get_post_terms($pid, $tax, ['fields' => 'ids']); if (!is_wp_error($cats) && !empty(array_intersect(array_map('intval', (array) $cats), $scope_ids))) { $matched = true; break 2; } } } if (!$matched) { return ['valid' => false, 'message' => __('该优惠码不适用于当前分类', 'aksrt')]; } } } } // Calculate discount $discount = 0; if ($coupon['type'] === self::TYPE_PERCENT) { $discount = $order_amount * ($coupon['value'] / 100); } else { $discount = min($coupon['value'], $order_amount); } // Max discount cap if ($coupon['max_discount'] > 0 && $discount > $coupon['max_discount']) { $discount = $coupon['max_discount']; } return [ 'valid' => true, 'coupon' => $coupon, 'discount' => round($discount, 2), ]; } /** * 核销优惠券 */ public function use_coupon(string $code): bool { global $wpdb; $code = sanitize_text_field($code); $result = $wpdb->query($wpdb->prepare( "UPDATE {$this->table()} SET used_count = used_count + 1 WHERE code = %s", $code )); return $result !== false; } /** * 禁用优惠券 */ public function disable(int $id): bool { global $wpdb; return $wpdb->update($this->table(), ['status' => 'disabled'], ['id' => $id], ['%s'], ['%d']) !== false; } public function enable(int $id): bool { global $wpdb; return $wpdb->update($this->table(), ['status' => 'active'], ['id' => $id], ['%s'], ['%d']) !== false; } /** * Batch-create coupons with auto-generated codes. * * @return array{created:int,codes:array} */ public function batch_create(array $data, int $count = 1, int $length = 10, string $prefix = ''): array { $count = max(1, min(100, $count)); $length = max(6, min(24, $length)); $prefix = sanitize_text_field($prefix); $codes = []; $created = 0; for ($i = 0; $i < $count; $i++) { $code = $prefix !== '' ? $prefix . strtoupper(wp_generate_password($length, false, false)) : strtoupper(wp_generate_password($length, false, false)); $payload = $data; $payload['code'] = $code; $id = $this->create($payload); if ($id > 0) { $created++; $codes[] = $code; } } return [ 'created' => $created, 'codes' => $codes, ]; } /** * Count how many times a user has used a specific coupon. */ public function get_user_usage_count(int $coupon_id, int $user_id): int { global $wpdb; $table = AKSRT_DB_Migrator::table('coupon_usage'); return (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE coupon_id = %d AND user_id = %d", $coupon_id, $user_id )); } /** * Log a coupon usage record. */ public function log_usage(int $coupon_id, int $user_id, string $order_number, float $discount): void { global $wpdb; $table = AKSRT_DB_Migrator::table('coupon_usage'); $wpdb->insert($table, [ 'coupon_id' => $coupon_id, 'user_id' => $user_id, 'order_number' => $order_number, 'discount' => $discount, 'created_at' => current_time('mysql'), ], ['%d', '%d', '%s', '%f', '%s']); } private function normalize_row(array $row): array { $row['id'] = (int) $row['id']; $row['value'] = (float) $row['value']; $row['min_amount'] = (float) $row['min_amount']; $row['usage_limit'] = (int) $row['usage_limit']; $row['used_count'] = (int) $row['used_count']; $row['per_user_limit'] = (int) ($row['per_user_limit'] ?? 0); $row['max_discount'] = (float) ($row['max_discount'] ?? 0); if (!empty($row['product_scope']) && is_string($row['product_scope'])) { $row['product_scope'] = json_decode($row['product_scope'], true) ?: []; } return $row; } }