1.新增获取充值/提现配置接口/api/finance/depositWithdrawConfig

2.优化充值和提现方式
This commit is contained in:
2026-04-23 10:15:01 +08:00
parent 0f28c0fd2a
commit aa1299c018
55 changed files with 901 additions and 750 deletions

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace app\api\controller;
use app\common\library\finance\DepositMockGateway;
use app\common\library\finance\DepositSettlement;
use app\common\library\finance\WithdrawFlow;
use app\common\library\game\DepositChannel as DepositChannelLib;
@@ -12,13 +13,26 @@ use app\common\library\game\FinanceCashierConfig as FinanceCashierConfigLib;
use app\common\model\DepositOrder;
use app\common\model\GameConfig;
use app\common\model\WithdrawOrder;
use app\common\service\DepositOrderExpireService;
use app\common\service\UserPushService;
use support\Response;
use support\think\Db;
use Throwable;
use Webman\Http\Request;
use function response;
class Finance extends MobileBase
{
/**
* 模拟第三方收银台页与支付回调,无需 user-token仅 HMAC 防篡改。
*/
protected array $noNeedLogin = ['depositMockPayPage', 'depositMockNotify'];
/**
* 允许浏览器直接打开 pay_url 而不带 auth-token。
*/
protected array $noNeedAuthToken = ['depositMockPayPage', 'depositMockNotify'];
/**
* 充值档位列表(仅启用档位,按 sort 升序)
*/
@@ -36,7 +50,7 @@ class Finance extends MobileBase
foreach ($tiers as $tier) {
$amount = $this->amountString($tier['amount'] ?? '0');
$bonus = $this->amountString($tier['bonus_amount'] ?? '0');
$total = bcadd($amount, $bonus, 4);
$total = bcadd($amount, $bonus, 2);
$payAmount = $this->amountString($tier['pay_amount'] ?? '0');
$currency = isset($tier['currency']) && is_string($tier['currency']) ? strtoupper(trim($tier['currency'])) : 'CNY';
if ($currency === '') {
@@ -49,10 +63,10 @@ class Finance extends MobileBase
'tier_key' => $tierId,
'title' => $localized['title'],
'currency' => $currency,
'pay_amount' => $payAmount,
'amount' => $amount,
'bonus_amount' => $bonus,
'total_amount' => $total,
'pay_amount' => $this->amountNumber($payAmount),
'amount' => $this->amountNumber($amount),
'bonus_amount' => $this->amountNumber($bonus),
'total_amount' => $this->amountNumber($total),
'desc' => $localized['desc'],
'channels' => DepositChannelLib::channelsForTier($tierId, $effectiveChannels, $lang),
];
@@ -77,15 +91,18 @@ class Finance extends MobileBase
/**
* 创建充值订单
*
* 当前为 mock 支付网关,点击即成功:服务端直接在同一请求内完成订单入账
* 未来接入真实第三方支付时,仅需把 "立即结算" 替换为 "返回 pay_url 进入网关"
* 并把入账动作放到网关回调里完成(回调中调用 DepositSettlement::settle
* 当前为 mock 支付网关:本接口仅创建待支付订单并返回 pay_url
* 未来接入真实第三方支付时,仅需替换 pay_url 生成与回调验签,入账仍在回调中调用 DepositSettlement::settle。
*
* 请求application/json 或 x-www-form-urlencoded
* - tier_id / tier_key: 必填,档位唯一标识(与 depositTierList 中 id、tier_key 一致)
* - channel_code: 必填,支付渠道代码(与 depositTierList 各档位 channels[].code 一致)
* - idempotency_key: 必填,客户端幂等键,短时间内重复提交只生成一次订单
*
* 流程:仅创建 `status=0` 的待支付订单,返回 `pay_url`(含签名的模拟「第三方收银台」页);玩家打开后点确认,
* 由服务端 `depositMockNotify` 模拟网关联调完成入账。未来接入真实三方时,将「打开 pay_url + 等回调」替换为
* 真网关,入账仍走 `DepositSettlement::settle`。
*
* 响应(统一结构,未来接入第三方也保持此形状):
* - order_no / amount / pay_channel / paid / pay_url / status / create_time / pay_time
*/
@@ -119,20 +136,33 @@ class Finance extends MobileBase
return $this->mobileError(2004, 'Pay channel not available');
}
// 幂等命中:直接返回已有订单
$user = $this->auth->getUser();
$userId = intval(strval($user->id));
// 先做超时清理,再做幂等命中与“最多三笔待支付”限制
DepositOrderExpireService::expirePendingOrders($userId, null);
// 幂等命中:直接返回已有订单(允许客户端重试拿回同一 pay_url
try {
$existing = DepositOrder::where('idempotency_key', $idempotencyKey)->find();
if ($existing) {
if (intval($existing->user_id) !== intval($this->auth->id)) {
return $this->mobileError(1002, 'Idempotency key conflict');
}
return $this->mobileSuccess($this->buildDepositResponse($existing));
return $this->mobileSuccess($this->buildDepositResponse($existing, $this->publicOriginFromRequest($request)));
}
} catch (Throwable $e) {
// 忽略幂等查询失败,继续创建
}
$user = $this->auth->getUser();
$pendingCount = DepositOrderExpireService::pendingCountByUserId($userId);
if ($pendingCount >= DepositOrderExpireService::MAX_PENDING_DEPOSIT) {
return $this->mobileError(2005, 'Too many pending deposit orders', [
'max_pending' => DepositOrderExpireService::MAX_PENDING_DEPOSIT,
'pending_count' => $pendingCount,
'expire_seconds' => DepositOrderExpireService::EXPIRE_SECONDS,
]);
}
$orderNo = 'DP' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
$curSnap = isset($tier['currency']) && is_string($tier['currency']) ? strtoupper(trim($tier['currency'])) : 'CNY';
if ($curSnap === '') {
@@ -157,7 +187,6 @@ class Finance extends MobileBase
$channelId = intval(strval($user->channel_id));
}
$orderId = 0;
try {
$order = DepositOrder::create([
'order_no' => $orderNo,
@@ -169,70 +198,207 @@ class Finance extends MobileBase
'status' => 0,
'pay_channel' => $channelCode,
'deposit_tier_id' => $tier['id'],
'proof_image' => '',
'pay_account_snapshot' => json_encode($tierSnapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'remark' => '',
'create_time' => $now,
'update_time' => $now,
]);
$orderId = intval($order->id);
} catch (Throwable $e) {
$msg = $e->getMessage();
if (stripos($msg, 'Duplicate') !== false && stripos($msg, 'uk_deposit_order_idem') !== false) {
$existing = DepositOrder::where('idempotency_key', $idempotencyKey)->find();
if ($existing) {
return $this->mobileSuccess($this->buildDepositResponse($existing));
return $this->mobileSuccess($this->buildDepositResponse($existing, $this->publicOriginFromRequest($request)));
}
}
return $this->mobileError(2000, $msg);
}
// Mock 网关:立即结算,入账到钱包
try {
DepositSettlement::settle(
$orderId,
DepositSettlement::SOURCE_MOCK_GATEWAY,
'mock gateway auto settled',
null,
'channel_code=' . $channelCode
);
} catch (Throwable $e) {
return $this->mobileError(2000, $e->getMessage());
}
$settled = DepositOrder::where('id', $orderId)->find();
if (!$settled) {
return $this->mobileError(2000, 'Order not found after settle');
}
return $this->mobileSuccess($this->buildDepositResponse($settled));
// 仅落待支付单;真实入账在模拟网关联调 depositMockNotify 中完成
return $this->mobileSuccess($this->buildDepositResponse($order, $this->publicOriginFromRequest($request)));
}
/**
* 将订单模型转换为统一的创建/详情响应数据
*
* @param string|null $publicOrigin 如 https://api.xxx.com待支付时用于拼完整 pay_url为 null 时仅返回以 / 开头的 path+query
*/
private function buildDepositResponse($order): array
private function buildDepositResponse($order, ?string $publicOrigin = null): array
{
$status = $this->mapDepositStatus($order->status);
$paid = $status === 'paid';
$amount = $this->amountString($order->amount);
$bonus = $this->amountString($order->bonus_amount);
$total = bcadd($amount, $bonus, 4);
$total = bcadd($amount, $bonus, 2);
$on = is_string($order->order_no) ? $order->order_no : strval($order->order_no);
$payUrl = '';
if ($this->intValue($order->status) === 0 && $on !== '') {
$payUrl = DepositMockGateway::payPageUrl($on, $publicOrigin);
}
return [
'order_no' => is_string($order->order_no) ? $order->order_no : strval($order->order_no),
'amount' => $amount,
'bonus_amount' => $bonus,
'total_amount' => $total,
'order_no' => $on,
'amount' => $this->amountNumber($amount),
'bonus_amount' => $this->amountNumber($bonus),
'total_amount' => $this->amountNumber($total),
'status' => $status,
'paid' => $paid,
'pay_channel' => is_string($order->pay_channel) ? $order->pay_channel : strval($order->pay_channel),
'pay_url' => '',
'pay_url' => $payUrl,
'create_time' => is_numeric(strval($order->create_time)) ? intval(strval($order->create_time)) : 0,
'pay_time' => is_numeric(strval($order->pay_time)) ? intval(strval($order->pay_time)) : 0,
];
}
/**
* 将任意金额输入归一化为 4 位小数字符串(不做类型强制转换)
* 根据请求拼出公网 origin用于给客户端直接可用的完整 pay_url。
*/
private function publicOriginFromRequest(Request $request): string
{
$proto = strtolower((string) $request->header('x-forwarded-proto', ''));
$https = $proto === 'https' || strtolower((string) $request->header('x-forwarded-ssl', '')) === 'on';
$scheme = $https ? 'https' : 'http';
$host = trim((string) $request->header('host', ''));
if ($host === '') {
$host = trim((string) ($request->header('x-forwarded-host', '')));
}
if ($host === '') {
$host = '127.0.0.1:8787';
}
return $scheme . '://' . $host;
}
/**
* 模拟第三方支付收银台HTML。玩家浏览器打开点击按钮即向 depositMockNotify 发起回调。
*/
public function depositMockPayPage(Request $request): Response
{
$response = $this->initializeMobile($request);
if ($response !== null) {
return $response;
}
$orderNo = $this->stringParam($request->input('order_no'));
$sign = $this->stringParam($request->input('sign'));
if ($orderNo === '' || $sign === '' || !DepositMockGateway::verifyOrderNo($orderNo, $sign)) {
return response('Invalid or expired payment link', 403, [
'Content-Type' => 'text/plain; charset=utf-8',
]);
}
$order = DepositOrder::where('order_no', $orderNo)->find();
if (!$order) {
return response('Order not found', 404, [
'Content-Type' => 'text/plain; charset=utf-8',
]);
}
DepositOrderExpireService::expirePendingOrders(null, $orderNo);
$order = DepositOrder::where('order_no', $orderNo)->find();
if (!$order) {
return response('Order not found', 404, [
'Content-Type' => 'text/plain; charset=utf-8',
]);
}
if ($this->intValue($order->status) !== 0) {
$st = $this->mapDepositStatus($order->status);
$msg = 'Order status: ' . $st;
if ($st === 'paid') {
$msg = 'This order is already paid. You can return to the app.';
}
$msgEsc = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
return response('<!doctype html><html><head><meta charset="utf-8"><title>充值</title></head><body><p>' . $msgEsc . '</p></body></html>', 200, [
'Content-Type' => 'text/html; charset=utf-8',
]);
}
$amount = $this->amountString($order->amount);
$bonus = $this->amountString($order->bonus_amount);
$noEsc = htmlspecialchars($orderNo, ENT_QUOTES, 'UTF-8');
$signEsc = htmlspecialchars($sign, ENT_QUOTES, 'UTF-8');
$payChannel = is_string($order->pay_channel) ? htmlspecialchars($order->pay_channel, ENT_QUOTES, 'UTF-8') : '';
$html = '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>模拟支付</title></head><body style="font-family:system-ui;padding:1rem">';
$html .= '<h2>模拟第三方收银台</h2>';
$html .= '<p>订单号:' . $noEsc . '</p>';
$html .= '<p>支付渠道:' . $payChannel . '</p>';
$html .= '<p>金额(法币/标价):' . htmlspecialchars($amount, ENT_QUOTES, 'UTF-8') . ' + 赠送 ' . htmlspecialchars($bonus, ENT_QUOTES, 'UTF-8') . '(币)</p>';
$html .= '<p>点击下方按钮即视为<strong>第三方支付成功</strong>,服务端会回调并到账。</p>';
$html .= '<form method="post" action="/api/finance/depositMockNotify" style="margin-top:1rem">';
$html .= '<input type="hidden" name="order_no" value="' . $noEsc . '">';
$html .= '<input type="hidden" name="sign" value="' . $signEsc . '">';
$html .= '<button type="submit" style="padding:0.5rem 1rem">确认支付(模拟成功)</button>';
$html .= '</form></body></html>';
return response($html, 200, [
'Content-Type' => 'text/html; charset=utf-8',
]);
}
/**
* 模拟第三方异步通知:验签后调用 DepositSettlement::settle 入账,并推送 wallet.changed。
*/
public function depositMockNotify(Request $request): Response
{
$response = $this->initializeMobile($request);
if ($response !== null) {
return $response;
}
$orderNo = $this->stringParam($request->input('order_no'));
$sign = $this->stringParam($request->input('sign'));
if ($orderNo === '' || $sign === '') {
return $this->mobileError(1001, 'Missing parameters');
}
if (!DepositMockGateway::verifyOrderNo($orderNo, $sign)) {
return $this->mobileError(1003, 'Invalid parameter value');
}
$order = DepositOrder::where('order_no', $orderNo)->find();
if (!$order) {
return $this->mobileError(2003, 'Order does not exist');
}
DepositOrderExpireService::expirePendingOrders(null, $orderNo);
$order = DepositOrder::where('order_no', $orderNo)->find();
if (!$order) {
return $this->mobileError(2003, 'Order does not exist');
}
if ($this->intValue($order->status) !== 0) {
return $this->mobileSuccess($this->buildDepositResponse($order, null));
}
$orderId = intval(strval($order->id));
if ($orderId <= 0) {
return $this->mobileError(2000, 'Order id invalid');
}
$pc = is_string($order->pay_channel) ? $order->pay_channel : strval($order->pay_channel);
try {
$result = DepositSettlement::settle(
$orderId,
DepositSettlement::SOURCE_THIRD_PARTY,
'mock third party notify',
null,
'channel_code=' . $pc
);
$uid = intval(strval($order->user_id));
if ($uid > 0) {
$coinAfter = is_string($result['balance_after'] ?? null) ? $result['balance_after'] : strval($result['balance_after'] ?? '0');
$credit = is_string($result['credit'] ?? null) ? $result['credit'] : strval($result['credit'] ?? '0');
UserPushService::publish($uid, UserPushService::EVT_WALLET_CHANGED, [
'reason' => 'deposit',
'ref_type' => 'deposit_order',
'ref_id' => (string) $orderId,
'order_no' => $orderNo,
'delta' => $credit,
'balance_after' => $coinAfter,
]);
}
} catch (Throwable $e) {
return $this->mobileError(2000, $e->getMessage());
}
$fresh = DepositOrder::where('order_no', $orderNo)->find();
if (!$fresh) {
return $this->mobileError(2000, 'Order not found after settle');
}
return $this->mobileSuccess($this->buildDepositResponse($fresh, null));
}
/**
* 将任意金额输入归一化为 2 位小数字符串(不做类型强制转换)
*/
private function amountString($raw): string
{
@@ -241,12 +407,17 @@ class Finance extends MobileBase
} elseif (is_int($raw) || is_float($raw)) {
$s = strval($raw);
} else {
return '0.0000';
return '0.00';
}
if ($s === '' || !is_numeric($s)) {
return '0.0000';
return '0.00';
}
return bcadd($s, '0', 4);
return bcadd($s, '0', 2);
}
private function amountNumber($raw): float
{
return floatval($this->amountString($raw));
}
/**
@@ -262,11 +433,12 @@ class Finance extends MobileBase
if ($orderNo === '') {
return $this->mobileError(1001, 'Missing parameters');
}
DepositOrderExpireService::expirePendingOrders(null, $orderNo);
$order = DepositOrder::where('order_no', $orderNo)->where('user_id', $this->auth->id)->find();
if (!$order) {
return $this->mobileError(2003, 'Order does not exist');
}
return $this->mobileSuccess($this->buildDepositResponse($order));
return $this->mobileSuccess($this->buildDepositResponse($order, $this->publicOriginFromRequest($request)));
}
/**
@@ -279,6 +451,7 @@ class Finance extends MobileBase
if ($response !== null) {
return $response;
}
DepositOrderExpireService::expirePendingOrders(intval(strval($this->auth->id)), null);
$page = $this->intValue($request->input('page', 1));
if ($page <= 0) {
$page = 1;
@@ -295,8 +468,8 @@ class Finance extends MobileBase
foreach ($paginate->items() as $row) {
$list[] = [
'order_no' => $row->order_no,
'amount' => $this->amountString($row->amount ?? '0'),
'bonus_amount' => $this->amountString($row->bonus_amount ?? '0'),
'amount' => $this->amountNumber($row->amount ?? '0'),
'bonus_amount' => $this->amountNumber($row->bonus_amount ?? '0'),
'status' => $this->mapDepositStatus($row->status ?? null),
];
}
@@ -324,14 +497,34 @@ class Finance extends MobileBase
if ($withdrawCoin === '' || $receiveAccount === '' || $receiveType === '' || $idempotencyKey === '') {
return $this->mobileError(1001, 'Missing parameters');
}
if (!is_numeric($withdrawCoin) || bccomp($withdrawCoin, '0', 4) <= 0) {
if (mb_strlen($idempotencyKey) > 64) {
return $this->mobileError(1002, 'Idempotency key is too long');
}
if (!is_numeric($withdrawCoin) || bccomp($withdrawCoin, '0', 2) <= 0) {
return $this->mobileError(1001, 'Invalid withdraw amount');
}
$withdrawCoin = bcadd($withdrawCoin, '0', 4);
$withdrawCoin = bcadd($withdrawCoin, '0', 2);
$user = $this->auth->getUser();
$userId = intval(strval($user->id));
// 幂等:相同 idempotency_key 重试直接返回已创建订单
$idemOrder = Db::name('withdraw_order')->where('idempotency_key', $idempotencyKey)->find();
if ($idemOrder) {
$idemUserId = is_numeric(strval($idemOrder['user_id'] ?? null)) ? intval(strval($idemOrder['user_id'])) : 0;
if ($idemUserId !== $userId) {
return $this->mobileError(1002, 'Idempotency key conflict');
}
$idemStatus = $this->intValue($idemOrder['status'] ?? 0);
return $this->mobileSuccess([
'order_no' => is_string($idemOrder['order_no'] ?? null) ? $idemOrder['order_no'] : strval($idemOrder['order_no'] ?? ''),
'status' => $this->mapWithdrawStatus($idemStatus),
'fee_coin' => $this->amountNumber($idemOrder['fee'] ?? '0'),
'actual_arrival_coin' => $this->amountNumber($idemOrder['actual_amount'] ?? '0'),
'risk_review_required' => $idemStatus === 0,
]);
}
// 待审核订单数限制:同一用户最多 MAX_PENDING_WITHDRAW 笔 status=0待审核
$pendingCount = Db::name('withdraw_order')
->where('user_id', $userId)
@@ -344,8 +537,8 @@ class Finance extends MobileBase
]);
}
$balanceBefore = bcadd(strval($user->coin ?? '0'), '0', 4);
if (bccomp($balanceBefore, $withdrawCoin, 4) < 0) {
$balanceBefore = bcadd(strval($user->coin ?? '0'), '0', 2);
if (bccomp($balanceBefore, $withdrawCoin, 2) < 0) {
return $this->mobileError(2001, 'Insufficient balance');
}
@@ -359,14 +552,14 @@ class Finance extends MobileBase
'bet_flow_coin' => $user->bet_flow_coin ?? '0',
]);
$maxWithdrawable = WithdrawFlow::maxWithdrawable($balanceBefore, $flowStatus);
if (bccomp($withdrawCoin, $maxWithdrawable, 4) > 0) {
if (bccomp($withdrawCoin, $maxWithdrawable, 2) > 0) {
return $this->mobileError(2002, 'Withdraw exceeds available bet flow', [
'max_withdrawable' => $maxWithdrawable,
'coin_balance' => $balanceBefore,
'bet_flow_coin' => $flowStatus['bet_flow_coin'],
'total_withdraw_coin' => WithdrawFlow::amountString($user->total_withdraw_coin ?? '0'),
'ratio' => $flowStatus['ratio'],
'max_withdraw_by_flow' => $flowStatus['flow_unlimited'] ? null : $flowStatus['max_withdraw_by_flow'],
'max_withdrawable' => $this->amountNumber($maxWithdrawable),
'coin_balance' => $this->amountNumber($balanceBefore),
'bet_flow_coin' => $this->amountNumber($flowStatus['bet_flow_coin']),
'total_withdraw_coin' => $this->amountNumber(WithdrawFlow::amountString($user->total_withdraw_coin ?? '0')),
'ratio' => floatval($flowStatus['ratio']),
'max_withdraw_by_flow' => $flowStatus['flow_unlimited'] ? null : $this->amountNumber($flowStatus['max_withdraw_by_flow']),
]);
}
@@ -376,9 +569,9 @@ class Finance extends MobileBase
: null;
$orderNo = 'WD' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
$feeCoin = bcmul($withdrawCoin, '0.005', 4);
$actualArrivalCoin = bcsub($withdrawCoin, $feeCoin, 4);
$balanceAfter = bcsub($balanceBefore, $withdrawCoin, 4);
$feeCoin = bcmul($withdrawCoin, '0.005', 2);
$actualArrivalCoin = bcsub($withdrawCoin, $feeCoin, 2);
$balanceAfter = bcsub($balanceBefore, $withdrawCoin, 2);
$now = time();
Db::startTrans();
@@ -399,11 +592,14 @@ class Finance extends MobileBase
$orderId = Db::name('withdraw_order')->insertGetId([
'order_no' => $orderNo,
'idempotency_key' => $idempotencyKey,
'user_id' => $userId,
'channel_id' => $channelId,
'amount' => $withdrawCoin,
'fee' => $feeCoin,
'actual_amount' => $actualArrivalCoin,
'receive_type' => $receiveType,
'receive_account' => $receiveAccount,
'status' => 0,
'review_admin_id' => null,
'review_time' => null,
@@ -436,8 +632,8 @@ class Finance extends MobileBase
return $this->mobileSuccess([
'order_no' => $orderNo,
'status' => 'pending_review',
'fee_coin' => $feeCoin,
'actual_arrival_coin' => $actualArrivalCoin,
'fee_coin' => $this->amountNumber($feeCoin),
'actual_arrival_coin' => $this->amountNumber($actualArrivalCoin),
'risk_review_required' => true,
]);
}
@@ -465,9 +661,11 @@ class Finance extends MobileBase
return $this->mobileSuccess([
'order_no' => $order->order_no,
'status' => $this->mapWithdrawStatus($statusCode),
'withdraw_coin' => $order->amount,
'fee_coin' => $order->fee,
'actual_arrival_coin' => $order->actual_amount,
'withdraw_coin' => $this->amountNumber($order->amount ?? '0'),
'fee_coin' => $this->amountNumber($order->fee ?? '0'),
'actual_arrival_coin' => $this->amountNumber($order->actual_amount ?? '0'),
'receive_type' => is_string($order->receive_type ?? null) ? $order->receive_type : strval($order->receive_type ?? ''),
'receive_account' => is_string($order->receive_account ?? null) ? $order->receive_account : strval($order->receive_account ?? ''),
'reject_reason' => $statusCode === 2 && $remark !== '' ? $remark : null,
'create_time' => $order->create_time,
'review_time' => $order->review_time,
@@ -500,7 +698,7 @@ class Finance extends MobileBase
foreach ($paginate->items() as $row) {
$list[] = [
'order_no' => $row->order_no,
'amount' => $this->amountString($row->amount ?? '0'),
'amount' => $this->amountNumber($row->amount ?? '0'),
'status' => $this->mapWithdrawStatus($row->status ?? null),
];
}
@@ -518,6 +716,19 @@ class Finance extends MobileBase
* 收银台配置:货币列表(含充值/提现汇率、支付渠道pay_channels、提现银行与文案供充值/提现页展示)
*/
public function cashierConfig(Request $request): Response
{
return $this->buildDepositWithdrawConfig($request);
}
/**
* 充值/提现配置(推荐新接口,兼容 cashierConfig 相同返回结构)
*/
public function depositWithdrawConfig(Request $request): Response
{
return $this->buildDepositWithdrawConfig($request);
}
private function buildDepositWithdrawConfig(Request $request): Response
{
$response = $this->initializeMobile($request);
if ($response !== null) {