RechargeChannel.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace App\Models;
  3. use App\Services\Payment\PaymentProviderCatalog;
  4. use App\Services\Payment\PaymentProviderService;
  5. use App\Constants\Util;
  6. use Illuminate\Database\Eloquent\SoftDeletes;
  7. class RechargeChannel extends BaseModel
  8. {
  9. use SoftDeletes;
  10. protected $table = 'recharge_channel';
  11. protected $fillable = ['id', 'data_type', 'from', 'key', 'name', 'type', 'rate', 'fee_fixed', 'min', 'max', 'fixed', 'sort', 'status', 'logo'];
  12. protected $hidden = [];
  13. /** 三方手续费组合方式:只按百分比、只收每笔固定费用、百分比 + 每笔固定费用。 */
  14. public const FEE_MODES = ['rate' => '百分比', 'fixed' => '每笔固定', 'rate_fixed' => '百分比+每笔固定'];
  15. /**
  16. * 三方手续费展示:fee = 金额 × rate + fee_fixed(rate 为小数),平台承担。
  17. * @return array{fee_mode: string, fee_mode_text: string, fee_rate_percent: string, fee_text: string}
  18. */
  19. public static function feeDisplay($rate, $fixed): array
  20. {
  21. $rate = self::decimal($rate, 6);
  22. $fixed = self::decimal($fixed, 4);
  23. $hasRate = bccomp($rate, '0', 6) > 0;
  24. $hasFixed = bccomp($fixed, '0', 4) > 0;
  25. $mode = $hasRate && $hasFixed ? 'rate_fixed' : ($hasFixed ? 'fixed' : ($hasRate ? 'rate' : 'none'));
  26. $percent = self::trimZeros(bcmul($rate, '100', 4));
  27. $parts = [];
  28. if ($hasRate) $parts[] = $percent . '%';
  29. if ($hasFixed) $parts[] = '每笔' . self::trimZeros($fixed) . '元';
  30. return [
  31. 'fee_mode' => $mode,
  32. 'fee_mode_text' => self::FEE_MODES[$mode] ?? '无手续费',
  33. 'fee_rate_percent' => $percent,
  34. 'fee_text' => $parts ? implode(' + ', $parts) : '无手续费',
  35. ];
  36. }
  37. private static function decimal($value, int $scale): string
  38. {
  39. if ($value === null || $value === '') return bcadd('0', '0', $scale);
  40. return is_string($value) && is_numeric($value) ? bcadd($value, '0', $scale) : number_format((float)$value, $scale, '.', '');
  41. }
  42. private static function trimZeros(string $value): string
  43. {
  44. return str_contains($value, '.') ? rtrim(rtrim($value, '0'), '.') : $value;
  45. }
  46. public function getLogoAttribute($value): string
  47. {
  48. return $value ? (string)Util::ensureUrl($value) : '';
  49. }
  50. public function scopeAvailable($query)
  51. {
  52. $query->where('status', 1);
  53. $statuses = PaymentProviderService::statuses();
  54. foreach (PaymentProviderCatalog::codes() as $code) {
  55. if (($statuses[$code] ?? 0) === 1) continue;
  56. foreach ([1, 2] as $direction) {
  57. $types = PaymentProviderCatalog::channelTypesForProvider($code, $direction);
  58. if ($types === []) continue;
  59. $query->whereNot(fn ($part) => $part->where('data_type', $direction)->whereIn('type', $types));
  60. }
  61. }
  62. return $query;
  63. }
  64. public function getFixedAttribute($value)
  65. {
  66. return $value ? explode(',', $value) : null;
  67. }
  68. public static function getChannel($data_type, $type = [])
  69. {
  70. $query = self::where('status', 1)->where('data_type', $data_type);
  71. if ($type) {
  72. $query = $query->whereIn('type', $type);
  73. }
  74. $statuses = PaymentProviderService::statuses();
  75. $channel = $query->orderBy('sort')->orderBy('id')->get(['type', 'name', 'from', 'data_type', 'status'])
  76. ->map(function ($row) use ($statuses) {
  77. $data = $row->toArray() + PaymentProviderCatalog::channelDisplay($row->toArray(), $statuses);
  78. return $data + ['disabled' => $data['company_status'] === 0,
  79. 'disabled_reason' => $data['company_status'] === 0 ? '支付公司已禁用' : ''];
  80. })->all();
  81. $channel = array_column($channel, null, 'type');
  82. return array_values($channel);
  83. }
  84. //获取充值通道
  85. public static function product($from = '')
  86. {
  87. $where['status'] = 1;
  88. $where['data_type'] = 1;
  89. if($from){
  90. $where['from'] = $from;
  91. }
  92. $list = self::where($where)->available()->orderBy('sort', 'asc')->get()->toArray();
  93. return array_column($list, null, 'key');
  94. }
  95. public static function getFormatChannel($data_type, $recharge_channel_group_id = 1)
  96. {
  97. $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1;
  98. $query = self::where('data_type', $data_type)->available();
  99. $field = 'recharge_type'; //充值类型
  100. if ($data_type == 2) {
  101. //提现类型
  102. $field = 'withdraw_type';
  103. }
  104. $group = RechargeChannelGroup::query()->find($recharge_channel_group_id);
  105. $types = $group ? (array)$group->{$field} : [];
  106. if (!$types) {
  107. return [];
  108. }
  109. $query = $query->whereIn('type', $types);
  110. $direction = (int)$data_type;
  111. $fields = ['id', 'data_type','name','type', 'min','max','fixed','rate'];
  112. if (\App\Services\Payment\RechargeChannelLogoService::supportsDirection($direction)) {
  113. $fields[] = 'logo';
  114. }
  115. $product = $query->orderBy('sort', 'asc')->orderBy('id')->select($fields)->get()->toArray();
  116. $logos = $direction === 1 ? \App\Services\Payment\RechargeChannelLogoService::configured(array_column($product, 'type')) : [];
  117. $list = [];
  118. foreach($product as $key => $pv) {
  119. if (isset($list[$pv['type']]['config'])) {
  120. $config = $list[$pv['type']]['config'];
  121. if (empty($config['range'])) {
  122. $config['range'][] = $config;
  123. }
  124. if ($pv['min'] < $config['min']) {
  125. $config['min'] = $pv['min'];
  126. }
  127. if ($pv['max'] > $config['max']) {
  128. $config['max'] = $pv['max'];
  129. }
  130. if ($pv['rate'] < $config['rate']) {
  131. $config['max_rate'] = $config['rate'];
  132. $config['min_rate'] = $pv['rate'];
  133. }
  134. if ($pv['rate'] > $config['rate']) {
  135. $config['max_rate'] = $pv['rate'];
  136. }
  137. $config['range'][] = $pv;
  138. } else {
  139. $config = $pv;
  140. }
  141. $channels = $list[$pv['type']]['channels'] ?? [];
  142. $channels[] = [
  143. 'id' => (int)$pv['id'],
  144. 'label' => lang($pv['name']),
  145. 'min' => $pv['min'],
  146. 'max' => $pv['max'],
  147. 'fixed' => $pv['fixed'],
  148. 'rate' => $pv['rate'],
  149. ];
  150. $list[$pv['type']] = [
  151. 'key' => $key,
  152. 'label' => lang($pv['name']),
  153. 'value' => $pv['type'],
  154. 'config' => $config ?? [],
  155. 'channels' => $channels,
  156. ];
  157. if (\App\Services\Payment\RechargeChannelLogoService::supportsDirection($direction)) {
  158. $logo = $direction === 1 && array_key_exists($pv['type'], $logos)
  159. ? $logos[$pv['type']]
  160. : ($config['logo'] ?? '');
  161. $list[$pv['type']]['logo'] = $logo ? (string)Util::ensureUrl($logo) : '';
  162. }
  163. }
  164. $list = array_column($list, null, 'key');
  165. return array_values($list);
  166. }
  167. //校验是否支持此提现方式
  168. public static function checkWithdrawChannel($type, $recharge_channel_group_id = 1) {
  169. $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1;
  170. $group = RechargeChannelGroup::query()->find($recharge_channel_group_id);
  171. $withdrawTypes = $group ? (array)$group->withdraw_type : [];
  172. if (!in_array($type, $withdrawTypes, true)) {
  173. return false;
  174. }
  175. return self::query()->where('data_type', 2)->where('type', $type)
  176. ->available()->orderBy('sort')->first() ?: false;
  177. }
  178. /**
  179. * 按通道ID取会员可用的充值通道:必须是充值通道、未停用、支付公司未禁用,且类型在会员充值分组内。
  180. * 不满足时返回 false。
  181. */
  182. public static function findRechargeChannel(int $id, $recharge_channel_group_id = 1) {
  183. $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1;
  184. $group = RechargeChannelGroup::query()->find($recharge_channel_group_id);
  185. $rechargeTypes = $group ? (array)$group->recharge_type : [];
  186. $row = self::query()->where('data_type', 1)->whereKey($id)->available()->first();
  187. if (!$row || !in_array($row->type, $rechargeTypes, true)) {
  188. return false;
  189. }
  190. return $row;
  191. }
  192. //校验是否支持此充值方式
  193. public static function checkRechargeChannel($type, $recharge_channel_group_id = 1) {
  194. $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1;
  195. $group = RechargeChannelGroup::query()->find($recharge_channel_group_id);
  196. $rechargeTypes = $group ? (array)$group->recharge_type : [];
  197. if (!in_array($type, $rechargeTypes, true)) {
  198. return false;
  199. }
  200. return self::query()->where('data_type', 1)->where('type', $type)
  201. ->available()->orderBy('sort')->first() ?: false;
  202. }
  203. }