积分与余额账务系统硬切重构
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Ledger;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LedgerCommand {
|
||||
/**
|
||||
* @param array<int,array{user_id:int,asset:string,delta_minor:int,entry_type:string,counterparty_user_id?:int,remark?:string}> $entries
|
||||
* @param array<string,mixed> $metadata
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $operation_key,
|
||||
public readonly string $reason,
|
||||
public readonly array $entries,
|
||||
public readonly string $ref_type = '',
|
||||
public readonly string $ref_key = '',
|
||||
public readonly int $actor_user_id = 0,
|
||||
public readonly array $metadata = []
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $metadata
|
||||
*/
|
||||
public static function transfer(
|
||||
string $operation_key,
|
||||
string $reason,
|
||||
int $sender_id,
|
||||
int $recipient_id,
|
||||
AssetType|string $asset,
|
||||
int $gross_minor,
|
||||
int $fee_minor,
|
||||
string $ref_type = '',
|
||||
string $ref_key = '',
|
||||
string $sender_remark = '',
|
||||
string $recipient_remark = '',
|
||||
array $metadata = []
|
||||
): self {
|
||||
$asset = AssetType::from_value($asset);
|
||||
if ($sender_id <= 0 || $recipient_id <= 0 || $sender_id === $recipient_id || $gross_minor <= 0 || $fee_minor < 0 || $fee_minor >= $gross_minor) {
|
||||
throw new \InvalidArgumentException('ledger_transfer_invalid');
|
||||
}
|
||||
$net_minor = $gross_minor - $fee_minor;
|
||||
$entries = [[
|
||||
'user_id' => $sender_id,
|
||||
'asset' => $asset->value,
|
||||
'delta_minor' => -$net_minor,
|
||||
'entry_type' => 'transfer_out',
|
||||
'counterparty_user_id' => $recipient_id,
|
||||
'remark' => $sender_remark,
|
||||
]];
|
||||
if ($fee_minor > 0) {
|
||||
$entries[] = [
|
||||
'user_id' => $sender_id,
|
||||
'asset' => $asset->value,
|
||||
'delta_minor' => -$fee_minor,
|
||||
'entry_type' => 'fee',
|
||||
'counterparty_user_id' => $recipient_id,
|
||||
'remark' => __('转账手续费', 'aksrt'),
|
||||
];
|
||||
}
|
||||
$entries[] = [
|
||||
'user_id' => $recipient_id,
|
||||
'asset' => $asset->value,
|
||||
'delta_minor' => $net_minor,
|
||||
'entry_type' => 'transfer_in',
|
||||
'counterparty_user_id' => $sender_id,
|
||||
'remark' => $recipient_remark,
|
||||
];
|
||||
$metadata['gross_minor'] = $gross_minor;
|
||||
$metadata['fee_minor'] = $fee_minor;
|
||||
$metadata['net_minor'] = $net_minor;
|
||||
return new self($operation_key, $reason, $entries, $ref_type, $ref_key, $sender_id, $metadata);
|
||||
}
|
||||
|
||||
public function validate() {
|
||||
if ($this->operation_key === '' || strlen($this->operation_key) > 191 || !preg_match('/^[A-Za-z0-9:._-]+$/', $this->operation_key)) {
|
||||
return new \WP_Error('aksrt_ledger_operation_key', __('账务操作标识无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if ($this->reason === '' || strlen($this->reason) > 64 || sanitize_key($this->reason) !== $this->reason) {
|
||||
return new \WP_Error('aksrt_ledger_reason', __('账务原因无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if ($this->entries === []) {
|
||||
return new \WP_Error('aksrt_ledger_entries', __('账务明细不能为空', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if ($this->actor_user_id < 0) {
|
||||
return new \WP_Error('aksrt_ledger_actor', __('账务操作者无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
foreach ($this->entries as $entry) {
|
||||
try {
|
||||
AssetType::from((string) ($entry['asset'] ?? ''));
|
||||
} catch (\ValueError $e) {
|
||||
return new \WP_Error('aksrt_ledger_asset', __('资产类型无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$user_id = $entry['user_id'] ?? null;
|
||||
$delta_minor = $entry['delta_minor'] ?? null;
|
||||
$entry_type = $entry['entry_type'] ?? null;
|
||||
if (!is_int($user_id) || $user_id <= 0
|
||||
|| !is_int($delta_minor) || $delta_minor === 0 || $delta_minor === PHP_INT_MIN
|
||||
|| !is_string($entry_type) || $entry_type === '' || strlen($entry_type) > 32 || sanitize_key($entry_type) !== $entry_type
|
||||
) {
|
||||
return new \WP_Error('aksrt_ledger_entry', __('账务明细无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if (isset($entry['counterparty_user_id']) && (!is_int($entry['counterparty_user_id']) || $entry['counterparty_user_id'] <= 0)) {
|
||||
return new \WP_Error('aksrt_ledger_entry', __('账务明细无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function payload_hash(): string {
|
||||
$payload = [
|
||||
'reason' => $this->reason,
|
||||
'entries' => $this->entries,
|
||||
'ref_type' => $this->ref_type,
|
||||
'ref_key' => $this->ref_key,
|
||||
'actor_user_id' => $this->actor_user_id,
|
||||
'metadata' => $this->metadata,
|
||||
];
|
||||
self::sort_recursive($payload);
|
||||
return hash('sha256', (string) wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
private static function sort_recursive(array &$value): void {
|
||||
foreach ($value as &$item) {
|
||||
if (is_array($item)) {
|
||||
self::sort_recursive($item);
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
if (!array_is_list($value)) {
|
||||
ksort($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user