feat(pay): card-pass redeem for VIP and balance (zibll-style)

Rewrite charge-card into typed single-use card_pass inventory with public redeem REST, user-center UI for balance/VIP, admin type-aware create/batch, and CAS mark-used fulfillment via Credits/activate_vip.
This commit is contained in:
2026-07-14 20:13:46 +08:00
parent 5d7b138c2c
commit d34c954b83
14 changed files with 936 additions and 132 deletions
@@ -334,6 +334,9 @@ class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
$item = $this->build_charge_card_admin_item($row);
if ($keyword !== '' && mb_stripos(implode(' ', array_filter([
(string) ($item['code'] ?? ''),
(string) ($item['password'] ?? ''),
(string) ($item['card'] ?? ''),
(string) ($item['type_label'] ?? ''),
(string) ($item['note'] ?? ''),
(string) ($item['used_by_user']['display_name'] ?? ''),
(string) ($item['used_by_user']['email'] ?? ''),
@@ -377,14 +380,19 @@ class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
}
$params = $this->get_json_params($request);
$code = sanitize_text_field((string) ($params['code'] ?? ''));
$amount = (float) ($params['amount'] ?? 0);
if ($code === '' || $amount <= 0) {
return $this->error('卡号和金额不能为空');
$type = sanitize_key((string) ($params['type'] ?? 'balance'));
if (!in_array($type, ['balance', 'vip', 'credits'], true)) {
$type = 'balance';
}
$password = sanitize_text_field((string) ($params['password'] ?? $params['code'] ?? ''));
if ($password === '') {
return $this->error('卡密不能为空');
}
$id = AKSRT_Charge_Card::get_instance()->create($params);
$id = AKSRT_Charge_Card::get_instance()->create(array_merge($params, [
'type' => $type,
'password' => $password,
]));
if ($id > 0) {
return $this->success([
@@ -393,7 +401,7 @@ class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
], 201);
}
return $this->error(__('创建失败(可能卡号已存在)', 'aksrt'));
return $this->error(__('创建失败(可能卡密已存在或参数无效)', 'aksrt'));
}
/**
@@ -405,35 +413,43 @@ class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
}
$params = $this->get_json_params($request);
$amount = (float) ($params['amount'] ?? 0);
$type = sanitize_key((string) ($params['type'] ?? 'balance'));
if (!in_array($type, ['balance', 'vip', 'credits'], true)) {
$type = 'balance';
}
$count = max(1, min(100, (int) ($params['count'] ?? 1)));
$length = max(8, min(32, (int) ($params['length'] ?? 16)));
$prefix = sanitize_text_field((string) ($params['prefix'] ?? ''));
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
$expires = sanitize_text_field((string) ($params['expires_at'] ?? ''));
if ($amount <= 0) {
return $this->error('金额必须大于 0');
$meta_fields = [
'amount' => (float) ($params['amount'] ?? 0),
'level' => (int) ($params['level'] ?? 1),
'days' => (int) ($params['days'] ?? $params['vip_days'] ?? 30),
'points' => (int) ($params['points'] ?? $params['credits'] ?? 0),
];
if ($type === 'balance' && $meta_fields['amount'] <= 0) {
return $this->error('余额卡金额必须大于 0');
}
if ($type === 'credits' && $meta_fields['points'] <= 0) {
return $this->error('积分卡积分必须大于 0');
}
$service = AKSRT_Charge_Card::get_instance();
$codes = [];
for ($i = 0; $i < $count; $i++) {
$code = $prefix . strtoupper(wp_generate_password($length, false));
$id = $service->create([
'code' => $code,
'amount' => $amount,
'expires_at' => $expires,
'note' => $note,
]);
if ($id > 0) {
$codes[] = $code;
}
}
$codes = AKSRT_Charge_Card::get_instance()->batch_create(
$type,
$meta_fields,
$count,
$length,
$prefix,
$expires,
$note
);
return $this->success([
'message' => sprintf(__('已生成 %d 张充值卡', 'aksrt'), count($codes)),
'data' => ['codes' => $codes, 'count' => count($codes)],
'data' => ['codes' => $codes, 'count' => count($codes), 'type' => $type],
]);
}
@@ -650,18 +666,48 @@ class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
$used_by = (int) ($row['used_by'] ?? 0);
$user = $used_by > 0 ? get_userdata($used_by) : null;
$status = sanitize_key((string) ($row['status'] ?? 'active'));
$type = sanitize_key((string) ($row['type'] ?? 'balance'));
$statusMap = [
'active' => __('未使用', 'aksrt'),
'used' => __('已使用', 'aksrt'),
'expired' => __('已过期', 'aksrt'),
'disabled' => __('已禁用', 'aksrt'),
];
$typeMap = [
'balance' => __('余额卡', 'aksrt'),
'vip' => __('会员卡', 'aksrt'),
'credits' => __('积分卡', 'aksrt'),
];
$meta = [];
if (!empty($row['meta'])) {
if (is_array($row['meta'])) {
$meta = $row['meta'];
} else {
$decoded = json_decode((string) $row['meta'], true);
if (is_array($decoded)) {
$meta = $decoded;
}
}
}
$password = (string) ($row['password'] ?? $row['code'] ?? '');
$card_no = (string) ($row['card'] ?? '');
return [
'id' => (int) ($row['id'] ?? 0),
'code' => (string) ($row['code'] ?? ''),
'code' => $password,
'password' => $password,
'card' => $card_no,
'type' => $type,
'type_label' => $typeMap[$type] ?? $type,
'amount' => (float) ($row['amount'] ?? 0),
'balance' => (float) ($row['balance'] ?? 0),
'meta' => $meta,
'level' => (int) ($meta['level'] ?? 0),
'days' => (int) ($meta['days'] ?? 0),
'points' => (int) ($meta['points'] ?? 0),
'order_number' => (string) ($row['order_number'] ?? ''),
'status' => $status,
'status_label' => $statusMap[$status] ?? $status,
'used_by' => $used_by,