积分与余额账务系统硬切重构
This commit is contained in:
1 parent
ca12226b39
commit
5cae5b5c93
159 files changed
+7275
-4718
No files matched your search
+96
-27
@@ -44,20 +44,29 @@ class Coupon {
|
||||
$type = self::TYPE_FIXED;
|
||||
}
|
||||
|
||||
$value = $type === self::TYPE_PERCENT
|
||||
? $this->normalize_percent_input($data['value'] ?? null)
|
||||
: $this->normalize_money_input($data['value'] ?? null);
|
||||
$min_amount = $this->normalize_money_input($data['min_amount'] ?? '0');
|
||||
$max_discount = $this->normalize_money_input($data['max_discount'] ?? '0');
|
||||
if ($value === null || $min_amount === null || $max_discount === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), [
|
||||
'code' => $code,
|
||||
'type' => $type,
|
||||
'value' => (float) ($data['value'] ?? 0),
|
||||
'min_amount' => (float) ($data['min_amount'] ?? 0),
|
||||
'value' => (string) $value,
|
||||
'min_amount' => $min_amount,
|
||||
'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),
|
||||
'max_discount' => $max_discount,
|
||||
'status' => 'active',
|
||||
'created_at' => current_time('mysql'),
|
||||
], ['%s', '%s', '%f', '%f', '%d', '%d', '%s', '%d', '%s', '%f', '%s', '%s']);
|
||||
], ['%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%s', '%s', '%s', '%s']);
|
||||
|
||||
return (int) $wpdb->insert_id;
|
||||
}
|
||||
@@ -114,9 +123,15 @@ class Coupon {
|
||||
*
|
||||
* @return array{valid: bool, coupon?: array, message?: string}
|
||||
*/
|
||||
public function validate(string $code, float $order_amount = 0, array $args = []): array {
|
||||
public function validate(string $code, string $order_amount = '0', array $args = []): array {
|
||||
global $wpdb;
|
||||
|
||||
try {
|
||||
$order_minor = \AKSRT\Ledger\AssetAmount::balance_minor($order_amount);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return ['valid' => false, 'message' => '订单金额格式无效'];
|
||||
}
|
||||
|
||||
$code = sanitize_text_field($code);
|
||||
if ($code === '') {
|
||||
return ['valid' => false, 'message' => '请输入优惠码'];
|
||||
@@ -145,7 +160,7 @@ class Coupon {
|
||||
return ['valid' => false, 'message' => '优惠码已用完'];
|
||||
}
|
||||
|
||||
if ($coupon['min_amount'] > 0 && $order_amount < $coupon['min_amount']) {
|
||||
if ($coupon['min_amount_minor'] > 0 && $order_minor < $coupon['min_amount_minor']) {
|
||||
return ['valid' => false, 'message' => '订单金额不满足最低消费要求'];
|
||||
}
|
||||
|
||||
@@ -196,22 +211,23 @@ class Coupon {
|
||||
}
|
||||
|
||||
// Calculate discount
|
||||
$discount = 0;
|
||||
$discount_minor = 0;
|
||||
if ($coupon['type'] === self::TYPE_PERCENT) {
|
||||
$discount = $order_amount * ($coupon['value'] / 100);
|
||||
$discount_minor = \AKSRT\Ledger\AssetAmount::percentage($order_minor, (int) $coupon['value']);
|
||||
} else {
|
||||
$discount = min($coupon['value'], $order_amount);
|
||||
$discount_minor = min((int) $coupon['value_minor'], $order_minor);
|
||||
}
|
||||
|
||||
// Max discount cap
|
||||
if ($coupon['max_discount'] > 0 && $discount > $coupon['max_discount']) {
|
||||
$discount = $coupon['max_discount'];
|
||||
if ($coupon['max_discount_minor'] > 0 && $discount_minor > $coupon['max_discount_minor']) {
|
||||
$discount_minor = $coupon['max_discount_minor'];
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => true,
|
||||
'coupon' => $coupon,
|
||||
'discount' => round($discount, 2),
|
||||
'discount' => \AKSRT\Ledger\AssetAmount::decimal_from_minor($discount_minor),
|
||||
'discount_minor' => $discount_minor,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -223,11 +239,11 @@ class Coupon {
|
||||
|
||||
$code = sanitize_text_field($code);
|
||||
$result = $wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$this->table()} SET used_count = used_count + 1 WHERE code = %s",
|
||||
"UPDATE {$this->table()} SET used_count = used_count + 1 WHERE code = %s AND status = 'active' AND (usage_limit = 0 OR used_count < usage_limit)",
|
||||
$code
|
||||
));
|
||||
|
||||
return $result !== false;
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +282,8 @@ class Coupon {
|
||||
*/
|
||||
public function update(int $id, array $data): bool {
|
||||
global $wpdb;
|
||||
if ($id <= 0 || !$this->get($id)) {
|
||||
$current = $id > 0 ? $this->get($id) : null;
|
||||
if (!$current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -282,16 +299,31 @@ class Coupon {
|
||||
$format[] = '%s';
|
||||
}
|
||||
if (array_key_exists('value', $data)) {
|
||||
$fields['value'] = (float) $data['value'];
|
||||
$format[] = '%f';
|
||||
$target_type = (string) ($fields['type'] ?? $current['type']);
|
||||
$value = $target_type === self::TYPE_PERCENT
|
||||
? $this->normalize_percent_input($data['value'])
|
||||
: $this->normalize_money_input($data['value']);
|
||||
if ($value === null) {
|
||||
return false;
|
||||
}
|
||||
$fields['value'] = (string) $value;
|
||||
$format[] = '%s';
|
||||
}
|
||||
if (array_key_exists('min_amount', $data)) {
|
||||
$fields['min_amount'] = (float) $data['min_amount'];
|
||||
$format[] = '%f';
|
||||
$value = $this->normalize_money_input($data['min_amount']);
|
||||
if ($value === null) {
|
||||
return false;
|
||||
}
|
||||
$fields['min_amount'] = $value;
|
||||
$format[] = '%s';
|
||||
}
|
||||
if (array_key_exists('max_discount', $data)) {
|
||||
$fields['max_discount'] = (float) $data['max_discount'];
|
||||
$format[] = '%f';
|
||||
$value = $this->normalize_money_input($data['max_discount']);
|
||||
if ($value === null) {
|
||||
return false;
|
||||
}
|
||||
$fields['max_discount'] = $value;
|
||||
$format[] = '%s';
|
||||
}
|
||||
if (array_key_exists('usage_limit', $data)) {
|
||||
$fields['usage_limit'] = (int) $data['usage_limit'];
|
||||
@@ -371,29 +403,66 @@ class Coupon {
|
||||
/**
|
||||
* Log a coupon usage record.
|
||||
*/
|
||||
public function log_usage(int $coupon_id, int $user_id, string $order_number, float $discount): void {
|
||||
public function log_usage(int $coupon_id, int $user_id, string $order_number, string $discount): bool {
|
||||
global $wpdb;
|
||||
$discount = $this->normalize_money_input($discount);
|
||||
if ($coupon_id <= 0 || $order_number === '' || $discount === null) {
|
||||
return false;
|
||||
}
|
||||
$table = \AKSRT\Core\DBMigrator::table('coupon_usage');
|
||||
$wpdb->insert($table, [
|
||||
return $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']);
|
||||
], ['%d', '%d', '%s', '%s', '%s']) === 1;
|
||||
}
|
||||
|
||||
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['type'] = in_array((string) ($row['type'] ?? ''), [self::TYPE_PERCENT, self::TYPE_FIXED], true)
|
||||
? (string) $row['type']
|
||||
: self::TYPE_FIXED;
|
||||
if ($row['type'] === self::TYPE_PERCENT) {
|
||||
$row['value'] = $this->normalize_percent_input((string) ($row['value'] ?? '')) ?? 0;
|
||||
$row['value_minor'] = 0;
|
||||
} else {
|
||||
$row['value'] = $this->normalize_money_input((string) ($row['value'] ?? '')) ?? '0.00';
|
||||
$row['value_minor'] = \AKSRT\Ledger\AssetAmount::balance_minor($row['value']);
|
||||
}
|
||||
$row['min_amount'] = $this->normalize_money_input((string) ($row['min_amount'] ?? '')) ?? '0.00';
|
||||
$row['min_amount_minor'] = \AKSRT\Ledger\AssetAmount::balance_minor($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);
|
||||
$row['max_discount'] = $this->normalize_money_input((string) ($row['max_discount'] ?? '')) ?? '0.00';
|
||||
$row['max_discount_minor'] = \AKSRT\Ledger\AssetAmount::balance_minor($row['max_discount']);
|
||||
if (!empty($row['product_scope']) && is_string($row['product_scope'])) {
|
||||
$row['product_scope'] = json_decode($row['product_scope'], true) ?: [];
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function normalize_money_input($value): ?string {
|
||||
if (!is_string($value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function normalize_percent_input($value): ?int {
|
||||
if (!is_string($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = trim($value);
|
||||
if (!preg_match('/^(100|[1-9]?\d)(?:\.0{1,2})?$/', $value)) {
|
||||
return null;
|
||||
}
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user