$entries * @param array $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 $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); } } }