'百分比', 'fixed' => '每笔固定', 'rate_fixed' => '百分比+每笔固定']; /** * 三方手续费展示:fee = 金额 × rate + fee_fixed(rate 为小数),平台承担。 * @return array{fee_mode: string, fee_mode_text: string, fee_rate_percent: string, fee_text: string} */ public static function feeDisplay($rate, $fixed): array { $rate = self::decimal($rate, 6); $fixed = self::decimal($fixed, 4); $hasRate = bccomp($rate, '0', 6) > 0; $hasFixed = bccomp($fixed, '0', 4) > 0; $mode = $hasRate && $hasFixed ? 'rate_fixed' : ($hasFixed ? 'fixed' : ($hasRate ? 'rate' : 'none')); $percent = self::trimZeros(bcmul($rate, '100', 4)); $parts = []; if ($hasRate) $parts[] = $percent . '%'; if ($hasFixed) $parts[] = '每笔' . self::trimZeros($fixed) . '元'; return [ 'fee_mode' => $mode, 'fee_mode_text' => self::FEE_MODES[$mode] ?? '无手续费', 'fee_rate_percent' => $percent, 'fee_text' => $parts ? implode(' + ', $parts) : '无手续费', ]; } private static function decimal($value, int $scale): string { if ($value === null || $value === '') return bcadd('0', '0', $scale); return is_string($value) && is_numeric($value) ? bcadd($value, '0', $scale) : number_format((float)$value, $scale, '.', ''); } private static function trimZeros(string $value): string { return str_contains($value, '.') ? rtrim(rtrim($value, '0'), '.') : $value; } public function getLogoAttribute($value): string { return $value ? (string)Util::ensureUrl($value) : ''; } public function scopeAvailable($query) { $query->where('status', 1); $statuses = PaymentProviderService::statuses(); foreach (PaymentProviderCatalog::codes() as $code) { if (($statuses[$code] ?? 0) === 1) continue; foreach ([1, 2] as $direction) { $types = PaymentProviderCatalog::channelTypesForProvider($code, $direction); if ($types === []) continue; $query->whereNot(fn ($part) => $part->where('data_type', $direction)->whereIn('type', $types)); } } return $query; } public function getFixedAttribute($value) { return $value ? explode(',', $value) : null; } public static function getChannel($data_type, $type = []) { $query = self::where('status', 1)->where('data_type', $data_type); if ($type) { $query = $query->whereIn('type', $type); } $statuses = PaymentProviderService::statuses(); $channel = $query->orderBy('sort')->orderBy('id')->get(['type', 'name', 'from', 'data_type', 'status']) ->map(function ($row) use ($statuses) { $data = $row->toArray() + PaymentProviderCatalog::channelDisplay($row->toArray(), $statuses); return $data + ['disabled' => $data['company_status'] === 0, 'disabled_reason' => $data['company_status'] === 0 ? '支付公司已禁用' : '']; })->all(); $channel = array_column($channel, null, 'type'); return array_values($channel); } //获取充值通道 public static function product($from = '') { $where['status'] = 1; $where['data_type'] = 1; if($from){ $where['from'] = $from; } $list = self::where($where)->available()->orderBy('sort', 'asc')->get()->toArray(); return array_column($list, null, 'key'); } public static function getFormatChannel($data_type, $recharge_channel_group_id = 1) { $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1; $query = self::where('data_type', $data_type)->available(); $field = 'recharge_type'; //充值类型 if ($data_type == 2) { //提现类型 $field = 'withdraw_type'; } $group = RechargeChannelGroup::query()->find($recharge_channel_group_id); $types = $group ? (array)$group->{$field} : []; if (!$types) { return []; } $query = $query->whereIn('type', $types); $direction = (int)$data_type; $fields = ['id', 'data_type','name','type', 'min','max','fixed','rate']; if (\App\Services\Payment\RechargeChannelLogoService::supportsDirection($direction)) { $fields[] = 'logo'; } $product = $query->orderBy('sort', 'asc')->orderBy('id')->select($fields)->get()->toArray(); $logos = $direction === 1 ? \App\Services\Payment\RechargeChannelLogoService::configured(array_column($product, 'type')) : []; $list = []; foreach($product as $key => $pv) { if (isset($list[$pv['type']]['config'])) { $config = $list[$pv['type']]['config']; if (empty($config['range'])) { $config['range'][] = $config; } if ($pv['min'] < $config['min']) { $config['min'] = $pv['min']; } if ($pv['max'] > $config['max']) { $config['max'] = $pv['max']; } if ($pv['rate'] < $config['rate']) { $config['max_rate'] = $config['rate']; $config['min_rate'] = $pv['rate']; } if ($pv['rate'] > $config['rate']) { $config['max_rate'] = $pv['rate']; } $config['range'][] = $pv; } else { $config = $pv; } $channels = $list[$pv['type']]['channels'] ?? []; $channels[] = [ 'id' => (int)$pv['id'], 'label' => lang($pv['name']), 'min' => $pv['min'], 'max' => $pv['max'], 'fixed' => $pv['fixed'], 'rate' => $pv['rate'], ]; $list[$pv['type']] = [ 'key' => $key, 'label' => lang($pv['name']), 'value' => $pv['type'], 'config' => $config ?? [], 'channels' => $channels, ]; if (\App\Services\Payment\RechargeChannelLogoService::supportsDirection($direction)) { $logo = $direction === 1 && array_key_exists($pv['type'], $logos) ? $logos[$pv['type']] : ($config['logo'] ?? ''); $list[$pv['type']]['logo'] = $logo ? (string)Util::ensureUrl($logo) : ''; } } $list = array_column($list, null, 'key'); return array_values($list); } //校验是否支持此提现方式 public static function checkWithdrawChannel($type, $recharge_channel_group_id = 1) { $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1; $group = RechargeChannelGroup::query()->find($recharge_channel_group_id); $withdrawTypes = $group ? (array)$group->withdraw_type : []; if (!in_array($type, $withdrawTypes, true)) { return false; } return self::query()->where('data_type', 2)->where('type', $type) ->available()->orderBy('sort')->first() ?: false; } /** * 按通道ID取会员可用的充值通道:必须是充值通道、未停用、支付公司未禁用,且类型在会员充值分组内。 * 不满足时返回 false。 */ public static function findRechargeChannel(int $id, $recharge_channel_group_id = 1) { $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1; $group = RechargeChannelGroup::query()->find($recharge_channel_group_id); $rechargeTypes = $group ? (array)$group->recharge_type : []; $row = self::query()->where('data_type', 1)->whereKey($id)->available()->first(); if (!$row || !in_array($row->type, $rechargeTypes, true)) { return false; } return $row; } //校验是否支持此充值方式 public static function checkRechargeChannel($type, $recharge_channel_group_id = 1) { $recharge_channel_group_id = $recharge_channel_group_id > 0 ? $recharge_channel_group_id : 1; $group = RechargeChannelGroup::query()->find($recharge_channel_group_id); $rechargeTypes = $group ? (array)$group->recharge_type : []; if (!in_array($type, $rechargeTypes, true)) { return false; } return self::query()->where('data_type', 1)->where('type', $type) ->available()->orderBy('sort')->first() ?: false; } }