WithdrawalDestination.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <?php
  2. namespace App\Services;
  3. use App\Constants\HttpStatus;
  4. use App\Services\Payment\JdPayService;
  5. use App\Services\Payment\NoPayService;
  6. use App\Services\Payment\QianBaoService;
  7. use App\Services\Payment\ZimuPayService;
  8. use Exception;
  9. final class WithdrawalDestination
  10. {
  11. public const MANUAL_METHODS = [
  12. 'bank' => '银行卡',
  13. 'wechat' => '微信',
  14. 'alipay' => '支付宝',
  15. ];
  16. /** 提现通道列表加上前端表单需要的字段(requires_* 和人工提现的收款方式),会员和代理共用。 */
  17. public static function describeChannels(array $list): array
  18. {
  19. foreach ($list as &$item) {
  20. $channel = $item['value'];
  21. $item['requires_real_name'] = true;
  22. $item['requires_bank_name'] = WithdrawalDestination::requiresBankName($channel);
  23. $item['requires_bank_branch'] = $channel === 'DF001';
  24. if ($channel === 'DF001') {
  25. $item['bank_name_label'] = lang('银行名称');
  26. $item['bank_branch_label'] = lang('开户行');
  27. }
  28. if ($channel === 'rgtx') {
  29. $item['payout_methods'] = [];
  30. foreach (WithdrawalDestination::MANUAL_METHODS as $value => $label) {
  31. $method = ['value' => $value, 'label' => lang($label),
  32. 'requires_bank_name' => $value === 'bank', 'requires_card_no' => $value !== 'wechat',
  33. 'requires_bank_branch' => $value === 'bank',
  34. 'requires_qr_image' => $value !== 'bank'];
  35. if ($value === 'bank') {
  36. $method['bank_name_label'] = lang('银行名称');
  37. $method['bank_branch_label'] = lang('开户行');
  38. }
  39. $item['payout_methods'][] = $method;
  40. }
  41. }
  42. }
  43. unset($item);
  44. return $list;
  45. }
  46. public static function requiresBankName(string $channel): bool
  47. {
  48. return $channel === QianBaoService::ALIPAY_TO_CARD || ZimuPayService::isCashChannel($channel);
  49. }
  50. public static function requiresBankBranch(string $channel, string $bankName = ''): bool
  51. {
  52. return $channel === QianBaoService::ALIPAY_TO_CARD
  53. || ($channel === 'rgtx' && self::manualMethod($bankName) === 'bank');
  54. }
  55. public static function bankBranch(string $channel, string $bankName, ?string $branch): ?string
  56. {
  57. if (!self::requiresBankBranch($channel, $bankName)) {
  58. return null;
  59. }
  60. $branch = trim($branch ?? '');
  61. if ($branch === '' || mb_strlen($branch) > 255) {
  62. throw new Exception(lang('请输入开户行支行名称'), HttpStatus::CUSTOM_ERROR);
  63. }
  64. return $branch;
  65. }
  66. public static function resolve(string $channel, array $params): array
  67. {
  68. $bankName = trim($params['bank_name'] ?? '');
  69. $cardNo = trim($params['card_no'] ?? '');
  70. if ($channel === 'rgtx') {
  71. $method = $params['payout_method'] ?? self::manualMethod($bankName);
  72. if ($method !== 'bank') {
  73. $image = $params['qr_image'] ?? ($method === 'wechat' ? $cardNo : '');
  74. if (!self::isQrImage($image)) {
  75. throw new Exception(lang('请上传有效的收款码图片'), HttpStatus::CUSTOM_ERROR);
  76. }
  77. if ($method === 'alipay' && ($cardNo === '' || filter_var($cardNo, FILTER_VALIDATE_URL))) {
  78. throw new Exception(lang('请输入支付宝账号'), HttpStatus::CUSTOM_ERROR);
  79. }
  80. return [
  81. 'bank_name' => self::MANUAL_METHODS[$method],
  82. 'card_no' => $method === 'alipay' ? $cardNo : '',
  83. 'qr_image' => $image,
  84. ];
  85. }
  86. if ($bankName === '' || self::manualMethod($bankName) !== 'bank') {
  87. throw new Exception(lang('请输入银行名称'), HttpStatus::CUSTOM_ERROR);
  88. }
  89. } elseif (self::requiresBankName($channel) && $bankName === '') {
  90. throw new Exception(lang('请输入银行名称'), HttpStatus::CUSTOM_ERROR);
  91. }
  92. if ($cardNo === '') {
  93. throw new Exception(lang('请输入收款账号'), HttpStatus::CUSTOM_ERROR);
  94. }
  95. $bankName = match (true) {
  96. $channel === QianBaoService::ALIPAY_TO_ALIPAY => '支付宝',
  97. $channel === QianBaoService::NUMBER_RMB => '数字人民币',
  98. ZimuPayService::isWithdrawChannel($channel) => $bankName ?: '808币提现-钱包地址',
  99. JdPayService::isChannel($channel) => $bankName ?: 'JD钱包',
  100. NoPayService::isWithdrawChannel($channel) => $bankName ?: 'NO钱包',
  101. default => $bankName,
  102. };
  103. return [
  104. 'bank_name' => $bankName,
  105. 'bank_branch' => self::bankBranch($channel, $bankName, $params['bank_branch'] ?? null),
  106. 'card_no' => $cardNo,
  107. ];
  108. }
  109. public static function manualResponse(string $bankName, string $cardNo, ?string $qrImage): array
  110. {
  111. $method = self::manualMethod($bankName);
  112. if ($method === 'bank') {
  113. return ['payout_method' => $method, 'card_no' => $cardNo, 'qr_image' => null];
  114. }
  115. if ($qrImage === null && self::isQrImage($cardNo)) {
  116. // Older QR-only records put the image in card_no.
  117. $qrImage = $cardNo;
  118. $cardNo = '';
  119. }
  120. return ['payout_method' => $method, 'card_no' => $method === 'wechat' ? '' : $cardNo, 'qr_image' => $qrImage];
  121. }
  122. private static function isQrImage(string $value): bool
  123. {
  124. return filter_var($value, FILTER_VALIDATE_URL) !== false && parse_url($value, PHP_URL_SCHEME) === 'https';
  125. }
  126. private static function manualMethod(string $bankName): string
  127. {
  128. return match ($bankName) {
  129. '微信' => 'wechat',
  130. '支付宝' => 'alipay',
  131. default => 'bank',
  132. };
  133. }
  134. }