feat(payment): rebuild order details and asset records
This commit is contained in:
1 parent
476e2f961d
commit
7b55cfb190
57 files changed
+2080
-1294
No files matched your search
@@ -1,198 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Finance;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class FinancialFlowQuery {
|
||||
|
||||
private static ?self $instance = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function order_payment_number(string $order_number): string {
|
||||
global $wpdb;
|
||||
$order_number = substr(sanitize_text_field($order_number), 0, 32);
|
||||
if ($order_number === '') {
|
||||
return '';
|
||||
}
|
||||
return (string) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT flow_number FROM {$this->flow_table()} WHERE order_number = %s AND event_type = 'order_payment' ORDER BY id ASC LIMIT 1",
|
||||
$order_number
|
||||
));
|
||||
}
|
||||
|
||||
/** @param array<int,string> $order_numbers @return array<string,string> */
|
||||
public function order_payment_number_map(array $order_numbers): array {
|
||||
global $wpdb;
|
||||
$order_numbers = array_values(array_unique(array_filter(array_map(static function ($number): string {
|
||||
return substr(sanitize_text_field((string) $number), 0, 32);
|
||||
}, $order_numbers))));
|
||||
if ($order_numbers === []) {
|
||||
return [];
|
||||
}
|
||||
$placeholders = implode(',', array_fill(0, count($order_numbers), '%s'));
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT order_number, flow_number FROM {$this->flow_table()} WHERE event_type = 'order_payment' AND order_number IN ({$placeholders}) ORDER BY id ASC",
|
||||
$order_numbers
|
||||
), ARRAY_A);
|
||||
$map = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$number = (string) ($row['order_number'] ?? '');
|
||||
if ($number !== '' && !isset($map[$number])) {
|
||||
$map[$number] = (string) ($row['flow_number'] ?? '');
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @return array{items:array<int,array<string,mixed>>,total:int,page:int,per_page:int}
|
||||
*/
|
||||
public function search(array $args = []): array {
|
||||
global $wpdb;
|
||||
$page = max(1, (int) ($args['page'] ?? 1));
|
||||
$per_page = min(100, max(1, (int) ($args['per_page'] ?? 20)));
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$where = ['1=1'];
|
||||
$params = [];
|
||||
$keyword = trim(sanitize_text_field((string) ($args['keyword'] ?? '')));
|
||||
if ($keyword !== '') {
|
||||
if ($this->keyword_hits_identifier($keyword)) {
|
||||
// 命中完整标识符:走等值查询。各列都有索引,避免 6 路 LIKE '%kw%' 全表扫描。
|
||||
$where[] = '(f.flow_number = %s OR f.operation_key = %s OR f.order_number = %s OR f.withdrawal_number = %s OR f.provider_transaction_id = %s OR f.ref_key = %s)';
|
||||
array_push($params, $keyword, $keyword, $keyword, $keyword, $keyword, $keyword);
|
||||
} else {
|
||||
// 未精确命中(含前缀、部分匹配、含中文或短关键字):退回模糊匹配。
|
||||
$like = '%' . $wpdb->esc_like($keyword) . '%';
|
||||
$where[] = '(f.flow_number LIKE %s OR f.operation_key LIKE %s OR f.order_number LIKE %s OR f.withdrawal_number LIKE %s OR f.provider_transaction_id LIKE %s OR f.ref_key LIKE %s)';
|
||||
array_push($params, $like, $like, $like, $like, $like, $like);
|
||||
}
|
||||
}
|
||||
$event_type = sanitize_key((string) ($args['event_type'] ?? ''));
|
||||
if ($event_type !== '') {
|
||||
$where[] = 'f.event_type = %s';
|
||||
$params[] = $event_type;
|
||||
}
|
||||
$user_id = max(0, (int) ($args['user_id'] ?? 0));
|
||||
if ($user_id > 0) {
|
||||
$where[] = "EXISTS (SELECT 1 FROM {$this->entry_table()} eu WHERE eu.flow_id = f.id AND (eu.user_id = %d OR eu.counterparty_user_id = %d))";
|
||||
array_push($params, $user_id, $user_id);
|
||||
}
|
||||
$asset = sanitize_key((string) ($args['asset'] ?? ''));
|
||||
if (in_array($asset, ['cash', 'balance', 'points'], true)) {
|
||||
$where[] = "EXISTS (SELECT 1 FROM {$this->entry_table()} ea WHERE ea.flow_id = f.id AND ea.asset = %s)";
|
||||
$params[] = $asset;
|
||||
}
|
||||
$direction = sanitize_key((string) ($args['direction'] ?? ''));
|
||||
if (in_array($direction, ['in', 'out'], true)) {
|
||||
$where[] = "EXISTS (SELECT 1 FROM {$this->entry_table()} ed WHERE ed.flow_id = f.id AND ed.direction = %s)";
|
||||
$params[] = $direction;
|
||||
}
|
||||
if (!empty($args['date_from'])) {
|
||||
$where[] = 'f.created_at >= %s';
|
||||
$params[] = (string) $args['date_from'];
|
||||
}
|
||||
if (!empty($args['date_to'])) {
|
||||
$where[] = 'f.created_at < %s';
|
||||
$params[] = (string) $args['date_to'];
|
||||
}
|
||||
$where_sql = implode(' AND ', $where);
|
||||
$count_sql = "SELECT COUNT(*) FROM {$this->flow_table()} f WHERE {$where_sql}";
|
||||
$total = (int) ($params ? $wpdb->get_var($wpdb->prepare($count_sql, $params)) : $wpdb->get_var($count_sql));
|
||||
$item_sql = "SELECT f.* FROM {$this->flow_table()} f WHERE {$where_sql} ORDER BY f.created_at DESC, f.id DESC LIMIT %d OFFSET %d";
|
||||
$rows = (array) $wpdb->get_results($wpdb->prepare($item_sql, array_merge($params, [$per_page, $offset])), ARRAY_A);
|
||||
$ids = array_values(array_filter(array_map(static fn(array $row): int => (int) ($row['id'] ?? 0), $rows)));
|
||||
$entry_map = $this->entry_map($ids);
|
||||
foreach ($rows as &$row) {
|
||||
$row['id'] = (int) ($row['id'] ?? 0);
|
||||
$row['ledger_transaction_id'] = (int) ($row['ledger_transaction_id'] ?? 0);
|
||||
$row['metadata'] = json_decode((string) ($row['metadata'] ?? ''), true) ?: [];
|
||||
$row['entries'] = $entry_map[$row['id']] ?? [];
|
||||
}
|
||||
unset($row);
|
||||
return ['items' => $rows, 'total' => $total, 'page' => $page, 'per_page' => $per_page];
|
||||
}
|
||||
|
||||
/** @return array<string,string> */
|
||||
public function event_types(): array {
|
||||
global $wpdb;
|
||||
$values = (array) $wpdb->get_col("SELECT DISTINCT event_type FROM {$this->flow_table()} ORDER BY event_type ASC");
|
||||
$out = [];
|
||||
foreach ($values as $value) {
|
||||
$key = sanitize_key((string) $value);
|
||||
if ($key !== '') {
|
||||
$out[$key] = $key;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @param array<int,int> $flow_ids @return array<int,array<int,array<string,mixed>>> */
|
||||
private function entry_map(array $flow_ids): array {
|
||||
global $wpdb;
|
||||
if ($flow_ids === []) {
|
||||
return [];
|
||||
}
|
||||
$placeholders = implode(',', array_fill(0, count($flow_ids), '%d'));
|
||||
$rows = (array) $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM {$this->entry_table()} WHERE flow_id IN ({$placeholders}) ORDER BY flow_id ASC, line_no ASC",
|
||||
$flow_ids
|
||||
), ARRAY_A);
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
foreach (['id', 'flow_id', 'line_no', 'user_id', 'delta_minor', 'balance_after_minor', 'counterparty_user_id'] as $field) {
|
||||
$row[$field] = (int) ($row[$field] ?? 0);
|
||||
}
|
||||
$map[$row['flow_id']][] = $row;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键字是否精确命中某个标识符列。
|
||||
*
|
||||
* 业务流水号统一为 {YmdHis}{8 位随机数},运营通常是从截图或邮件里整段复制,
|
||||
* 此时等值查询可以走索引。若没有精确命中,则退回 LIKE 模糊匹配,保证
|
||||
* 「只输入前缀也能搜到」的既有行为不变。
|
||||
*/
|
||||
private function keyword_hits_identifier(string $keyword): bool {
|
||||
global $wpdb;
|
||||
// 只对像标识符的输入启用:纯 ASCII 字母数字、长度 8-191。
|
||||
if (!preg_match('/^[A-Za-z0-9_-]{8,191}$/', $keyword)) {
|
||||
return false;
|
||||
}
|
||||
$found = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT 1 FROM {$this->flow_table()}
|
||||
WHERE flow_number = %s OR operation_key = %s OR order_number = %s
|
||||
OR withdrawal_number = %s OR provider_transaction_id = %s OR ref_key = %s
|
||||
LIMIT 1",
|
||||
$keyword,
|
||||
$keyword,
|
||||
$keyword,
|
||||
$keyword,
|
||||
$keyword,
|
||||
$keyword
|
||||
));
|
||||
return $found !== null;
|
||||
}
|
||||
|
||||
private function flow_table(): string {
|
||||
return \AKSRT\Core\DBMigrator::table('financial_flow');
|
||||
}
|
||||
|
||||
private function entry_table(): string {
|
||||
return \AKSRT\Core\DBMigrator::table('financial_flow_entry');
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,11 @@ final class FinancialFlowService {
|
||||
*/
|
||||
public function record_ledger(LedgerCommand $command, int $transaction_id, array $entries, string $created_at) {
|
||||
$order_number = $command->ref_type === 'order' ? $command->ref_key : (string) ($command->metadata['order_number'] ?? '');
|
||||
$order = $order_number !== '' ? $this->resolve_order($order_number) : [];
|
||||
$withdrawal_number = '';
|
||||
if ($command->ref_type === 'withdraw') {
|
||||
$withdrawal_number = $this->resolve_withdrawal_number($command, $transaction_id);
|
||||
}
|
||||
$provider_transaction_id = (string) ($order['payment_transaction_id'] ?? '');
|
||||
$provider_transaction_id = '';
|
||||
|
||||
$flow_entries = [];
|
||||
foreach ($entries as $entry) {
|
||||
@@ -54,26 +53,6 @@ final class FinancialFlowService {
|
||||
}
|
||||
$event_type = $command->reason;
|
||||
$metadata = $command->metadata;
|
||||
if ($order !== [] && $this->is_successful_order_payment($order, $flow_entries)) {
|
||||
$event_type = 'order_payment';
|
||||
$metadata['ledger_reason'] = $command->reason;
|
||||
if ($this->is_external_recharge($order, $flow_entries)) {
|
||||
try {
|
||||
$cash_minor = \AKSRT\Ledger\AssetAmount::balance((string) ($order['amount'] ?? ''))->minor();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return new \WP_Error('aksrt_flow_amount', __('充值支付流水金额无效', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
$flow_entries[] = [
|
||||
'user_id' => max(0, (int) ($order['user_id'] ?? 0)),
|
||||
'asset' => 'cash',
|
||||
'direction' => 'out',
|
||||
'entry_type' => 'payment',
|
||||
'delta_minor' => -$cash_minor,
|
||||
'counterparty_user_id' => 0,
|
||||
'remark' => __('充值订单支付', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->record([
|
||||
'operation_key' => $command->operation_key,
|
||||
@@ -90,78 +69,6 @@ final class FinancialFlowService {
|
||||
}
|
||||
|
||||
/** @return string|\WP_Error */
|
||||
public function record_order_payment(array $order, bool $caller_transaction = false) {
|
||||
return $this->record_order_event($order, 'order_payment', -1, $caller_transaction);
|
||||
}
|
||||
|
||||
/** @return string|\WP_Error */
|
||||
public function record_order_refund(array $order, bool $caller_transaction = false) {
|
||||
return $this->record_order_event($order, 'order_refund', 1, $caller_transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录卡密核销产生的订单支付流水。
|
||||
*
|
||||
* 卡密是平台自发的预付凭证:核销那一刻既没有第三方现金流入,也不产生账本资产分录,
|
||||
* 订单金额还可能为 0(会员卡)或以积分计价(积分卡)。因此这里只落一条与订单关联的
|
||||
* order_payment 流水,不生成任何现金/资产分录,让订单在详情页具备可追溯的「账本流水号」。
|
||||
*
|
||||
* 余额卡与积分卡的核销走账本(LedgerService::credit),其流水由 record_ledger() 落库;
|
||||
* 本方法供会员卡这类无账本参与的卡种调用。
|
||||
*
|
||||
* @param array<string,mixed> $order
|
||||
* @return string|\WP_Error
|
||||
*/
|
||||
public function record_card_pass_payment(array $order, bool $caller_transaction = false) {
|
||||
return $this->record_order_event($order, 'order_payment', -1, $caller_transaction, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把账本流水挂到卡密核销订单上(订单号 + 渠道交易号 + 事件类型归一)。
|
||||
*
|
||||
* 卡密核销的资产入账走账本,但订单号是随机生成的、不能进 LedgerCommand
|
||||
* (payload_hash 覆盖 metadata,塞入随机号会破坏账本幂等重放)。因此账本
|
||||
* 提交之后再调用本方法补挂关联;同时把 event_type 归一为 order_payment,
|
||||
* 让订单详情页的「账本流水号」能反查到它。
|
||||
*
|
||||
* @return bool 是否发生了更新
|
||||
*/
|
||||
public function attach_order_to_ledger_flow(int $ledger_transaction_id, string $order_number, string $provider_transaction_id = ''): bool {
|
||||
global $wpdb;
|
||||
if ($ledger_transaction_id <= 0 || $order_number === '') {
|
||||
return false;
|
||||
}
|
||||
$order_number = substr(sanitize_text_field($order_number), 0, 32);
|
||||
$order = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT status, payment_method, payment_transaction_id FROM {$this->order_table()} WHERE order_number = %s LIMIT 1",
|
||||
$order_number
|
||||
), ARRAY_A);
|
||||
if (!is_array($order)
|
||||
|| sanitize_key((string) ($order['status'] ?? '')) !== \AKSRT\Payment\Order::STATUS_PAID
|
||||
|| sanitize_key((string) ($order['payment_method'] ?? '')) !== 'card_pass'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if ($provider_transaction_id === '') {
|
||||
$provider_transaction_id = (string) ($order['payment_transaction_id'] ?? '');
|
||||
}
|
||||
$updated = $wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$this->flow_table()}
|
||||
SET order_number = %s,
|
||||
provider_transaction_id = CASE WHEN provider_transaction_id IS NULL OR provider_transaction_id = '' THEN %s ELSE provider_transaction_id END,
|
||||
event_type = 'order_payment'
|
||||
WHERE ledger_transaction_id = %d AND (order_number IS NULL OR order_number = '')",
|
||||
$order_number,
|
||||
substr(sanitize_text_field($provider_transaction_id), 0, 128),
|
||||
$ledger_transaction_id
|
||||
));
|
||||
return $updated === 1;
|
||||
}
|
||||
|
||||
private function order_table(): string {
|
||||
return \AKSRT\Core\DBMigrator::table('order');
|
||||
}
|
||||
|
||||
/** @return string|\WP_Error */
|
||||
public function record_withdrawal_payout(array $withdrawal, string $operation_key, string $created_at, bool $caller_transaction = false) {
|
||||
$user_id = max(0, (int) ($withdrawal['user_id'] ?? 0));
|
||||
@@ -334,79 +241,6 @@ final class FinancialFlowService {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $order
|
||||
* @param bool $voucher_settled 凭证结算(卡密核销):订单可能为 0 元或以积分计价,
|
||||
* 且没有任何现金/资产分录,故跳过金额与资产口径校验。
|
||||
*/
|
||||
private function record_order_event(array $order, string $event_type, int $sign, bool $caller_transaction, bool $voucher_settled = false) {
|
||||
$order_number = substr(sanitize_text_field((string) ($order['order_number'] ?? '')), 0, 32);
|
||||
$user_id = max(0, (int) ($order['user_id'] ?? 0));
|
||||
if ($order_number === '') {
|
||||
return new \WP_Error('aksrt_flow_order', __('订单流水参数无效', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
$existing = $this->number_for_order_event($order_number, $event_type);
|
||||
if ($existing !== '') {
|
||||
return $existing;
|
||||
}
|
||||
$payment_method = sanitize_key((string) ($order['payment_method'] ?? ''));
|
||||
$currency = sanitize_key((string) ($order['currency'] ?? 'CNY'));
|
||||
$metadata = [
|
||||
'payment_method' => $payment_method,
|
||||
'currency' => $currency,
|
||||
];
|
||||
$entries = [];
|
||||
if ($voucher_settled) {
|
||||
// 卡密核销:凭证本身即支付凭据,订单金额可能为 0、也可能以积分计价,
|
||||
// 因此不做金额校验、也不生成现金分录;流水行即该订单的支付凭证
|
||||
// (订单详情页经 FinancialFlowQuery::order_payment_number() 反查得到)。
|
||||
$metadata['voucher'] = 'card_pass';
|
||||
} else {
|
||||
if (in_array($payment_method, ['balance', 'points'], true) || $currency === 'points') {
|
||||
return new \WP_Error('aksrt_flow_order_asset', __('余额或积分订单必须通过资产账本确认', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
try {
|
||||
$amount_minor = \AKSRT\Ledger\AssetAmount::balance((string) ($order['amount'] ?? ''))->minor();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return new \WP_Error('aksrt_flow_amount', __('订单流水金额无效', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
if ($amount_minor <= 0) {
|
||||
return new \WP_Error('aksrt_flow_amount', __('订单流水金额无效', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
$entries[] = [
|
||||
'user_id' => $user_id,
|
||||
'asset' => 'cash',
|
||||
'direction' => $sign > 0 ? 'in' : 'out',
|
||||
'entry_type' => $event_type === 'order_payment' ? 'payment' : 'refund',
|
||||
'delta_minor' => $sign * $amount_minor,
|
||||
'remark' => $event_type === 'order_payment' ? __('订单支付', 'aksrt') : __('订单退款', 'aksrt'),
|
||||
];
|
||||
}
|
||||
$operation_key = \AKSRT\Ledger\OperationKey::deterministic(
|
||||
$event_type === 'order_payment' ? 'order-payment' : 'order-refund',
|
||||
[$order_number]
|
||||
);
|
||||
return $this->record([
|
||||
'operation_key' => $operation_key,
|
||||
'event_type' => $event_type,
|
||||
'ref_type' => 'order',
|
||||
'ref_key' => $order_number,
|
||||
'order_number' => $order_number,
|
||||
'provider_transaction_id' => (string) ($order['payment_transaction_id'] ?? ''),
|
||||
'metadata' => $metadata,
|
||||
'created_at' => self::utc_now(),
|
||||
], $entries, $caller_transaction, $voucher_settled);
|
||||
}
|
||||
|
||||
private function number_for_order_event(string $order_number, string $event_type): string {
|
||||
global $wpdb;
|
||||
return (string) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT flow_number FROM {$this->flow_table()} WHERE order_number = %s AND event_type = %s ORDER BY id ASC LIMIT 1",
|
||||
$order_number,
|
||||
$event_type
|
||||
));
|
||||
}
|
||||
|
||||
private function resolve_withdrawal_number(LedgerCommand $command, int $transaction_id): string {
|
||||
global $wpdb;
|
||||
$table = \AKSRT\Core\DBMigrator::table('withdrawal');
|
||||
@@ -420,55 +254,6 @@ final class FinancialFlowService {
|
||||
return (string) $wpdb->get_var($wpdb->prepare("SELECT withdrawal_number FROM {$table} WHERE ledger_transaction_id = %d LIMIT 1", $transaction_id));
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private function resolve_order(string $order_number): array {
|
||||
global $wpdb;
|
||||
$table = \AKSRT\Core\DBMigrator::table('order');
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT order_number, user_id, product_type, amount, currency, status, payment_method, payment_transaction_id FROM {$table} WHERE order_number = %s LIMIT 1",
|
||||
$order_number
|
||||
), ARRAY_A);
|
||||
return is_array($row) ? $row : [];
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $order @param array<int,array<string,mixed>> $entries */
|
||||
private function is_successful_order_payment(array $order, array $entries): bool {
|
||||
if (sanitize_key((string) ($order['status'] ?? '')) !== \AKSRT\Payment\Order::STATUS_PAID) {
|
||||
return false;
|
||||
}
|
||||
// card_pass:卡密即支付凭据,订单已支付就说明凭证已结算,不依赖现金或资产分录为凭。
|
||||
// 余额卡/积分卡的核销因此也能落成 order_payment 流水,与普通订单同口径。
|
||||
if (sanitize_key((string) ($order['payment_method'] ?? '')) === 'card_pass') {
|
||||
return true;
|
||||
}
|
||||
$buyer_id = max(0, (int) ($order['user_id'] ?? 0));
|
||||
foreach ($entries as $entry) {
|
||||
if ((int) ($entry['user_id'] ?? 0) === $buyer_id && (int) ($entry['delta_minor'] ?? 0) < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return $this->is_external_recharge($order, $entries);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $order @param array<int,array<string,mixed>> $entries */
|
||||
private function is_external_recharge(array $order, array $entries): bool {
|
||||
$method = sanitize_key((string) ($order['payment_method'] ?? ''));
|
||||
// 这些方式都在平台内部结算,核销当下没有第三方现金流入:balance / points 以已有资产支付,
|
||||
// card_pass 以平台自发凭证支付(现金在售卡时已确认),故都不生成现金分录。
|
||||
if (sanitize_key((string) ($order['product_type'] ?? '')) !== \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
||||
|| in_array($method, ['', 'balance', 'points', 'card_pass'], true)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
$buyer_id = max(0, (int) ($order['user_id'] ?? 0));
|
||||
foreach ($entries as $entry) {
|
||||
if ((int) ($entry['user_id'] ?? 0) === $buyer_id && (string) ($entry['asset'] ?? '') === 'balance' && (int) ($entry['delta_minor'] ?? 0) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function flow_table(): string {
|
||||
return \AKSRT\Core\DBMigrator::table('financial_flow');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user