From 9ea99da9c6d09717164b89cbc27ecb40888ad1e3 Mon Sep 17 00:00:00 2001 From: gaofeng <1212121@qq.com> Date: Fri, 4 Sep 2026 14:25:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/miniapi/controller/VisaApplication.php | 27 ++++++++++++++++++- .../service/Payment/MiniPayService.php | 23 ++++++++++++++++ .../service/Payment/PaymentChannelService.php | 1 + .../VisaApplicationService.php | 14 ++++++++++ config/mini_payment.php | 26 ++++++++++++++++++ tests/visa_application_facade_self_check.php | 9 +++++++ 6 files changed, 99 insertions(+), 1 deletion(-) diff --git a/app/miniapi/controller/VisaApplication.php b/app/miniapi/controller/VisaApplication.php index d1c9cb8..ce39c68 100644 --- a/app/miniapi/controller/VisaApplication.php +++ b/app/miniapi/controller/VisaApplication.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace app\miniapi\controller; +use app\miniapi\service\Payment\MiniPayService; use app\miniapi\service\VisaApplication\VisaApplicationService; final class VisaApplication extends Base @@ -11,7 +12,31 @@ final class VisaApplication extends Base public function start() { return $this->call(fn($service, $userId) => $service->start($userId, $this->postData(), $this->orderSource())); } public function application() { return $this->call(fn($service, $userId) => $service->application($userId, trim((string)$this->request->param('public_id', '')))); } public function draft() { return $this->call(fn($service, $userId) => $service->saveDraft($userId, $this->postData()), '草稿已保存'); } - public function submit() { return $this->call(fn($service, $userId) => $service->submit($userId, $this->postData()), '提交成功'); } + public function submit() + { + $userId = $this->requireLogin(); if (!is_int($userId)) return $userId; + $input = $this->postData(); + $result = (new VisaApplicationService())->submit($userId, $input); + if ((int)($result['code'] ?? 0) !== 1) return $this->fail((string)($result['msg'] ?? '服务异常,请稍后重试')); + $data = (array)($result['data'] ?? []); + if (!empty($data['requires_payment'])) { + $target = VisaApplicationService::nativePaymentForCheck($data); + $openid = trim((string)($input['openid'] ?? '')); + if (!$target || $openid === '') return $this->fail($openid === '' ? '微信用户信息缺失,请重新登录后支付' : '支付订单信息异常'); + $pay = (new MiniPayService())->requestForTarget( + $this->currentMiniAppContext('visa'), + $target['order_sn'], + $target['money'], + $openid, + $target['target_table'], + 'miniapi visa mini pay' + ); + if ((int)($pay['code'] ?? 0) !== 1) return $this->fail((string)($pay['msg'] ?? '支付参数获取失败')); + $data['pay'] = $pay['data']; + } + unset($data['target_orders'], $data['pay_url']); + return $this->ok($data, '提交成功'); + } public function agree() { $result = (new VisaApplicationService())->agree(trim((string)$this->request->param('doc_code', ''))); diff --git a/app/miniapi/service/Payment/MiniPayService.php b/app/miniapi/service/Payment/MiniPayService.php index a8983db..ad8092c 100644 --- a/app/miniapi/service/Payment/MiniPayService.php +++ b/app/miniapi/service/Payment/MiniPayService.php @@ -12,6 +12,29 @@ class MiniPayService public function request(MiniAppContext $context, string $orderSn, $money, string $openid, string $scene = 'miniapi mini pay'): array { $payConfig = (new PaymentChannelService())->miniPay($context); + return $this->send($payConfig, $orderSn, $money, $openid, $scene); + } + + public function requestForTarget(MiniAppContext $context, string $orderSn, $money, string $openid, string $targetTable, string $scene = 'miniapi target mini pay'): array + { + $payConfig = (new PaymentChannelService())->miniPay($context); + $mark = self::targetMarkForCheck((string)$payConfig['mark_prefix'], $targetTable); + if ($mark === '') { + return ['code' => 0, 'msg' => '支付配置未完成', 'data' => []]; + } + $payConfig['mark'] = $mark; + return $this->send($payConfig, $orderSn, $money, $openid, $scene); + } + + public static function targetMarkForCheck(string $prefix, string $targetTable): string + { + return preg_match('/^[A-Za-z0-9_]+$/', $prefix) && preg_match('/^[A-Za-z0-9_]+$/', $targetTable) + ? $prefix . $targetTable + : ''; + } + + private function send(array $payConfig, string $orderSn, $money, string $openid, string $scene): array + { if ($payConfig['url'] === '') { return [ 'code' => 0, diff --git a/app/miniapi/service/Payment/PaymentChannelService.php b/app/miniapi/service/Payment/PaymentChannelService.php index ed80d6e..f1eeea7 100644 --- a/app/miniapi/service/Payment/PaymentChannelService.php +++ b/app/miniapi/service/Payment/PaymentChannelService.php @@ -25,6 +25,7 @@ class PaymentChannelService 'url' => $url, 'body' => trim((string)($config['body'] ?? 'EVUS登记服务费')) ?: 'EVUS登记服务费', 'mark' => trim((string)($config['mark'] ?? $config['attach'] ?? 'msg')) ?: 'msg', + 'mark_prefix' => trim((string)($config['mark_prefix'] ?? '')), ]; } diff --git a/app/miniapi/service/VisaApplication/VisaApplicationService.php b/app/miniapi/service/VisaApplication/VisaApplicationService.php index aa69fba..8689f9b 100644 --- a/app/miniapi/service/VisaApplication/VisaApplicationService.php +++ b/app/miniapi/service/VisaApplication/VisaApplicationService.php @@ -22,6 +22,20 @@ final class VisaApplicationService return $this->request('agree', 0, ['doc_code' => $docCode]); } public static function decorateSubmitForCheck(array $result): array { if ((int)($result['code'] ?? 0) === 1 && !empty($result['data']['requires_payment']) && !empty($result['data']['order_sn'])) $result['data']['pay_url'] = 'https://www.qlevisa.com/index/pay/h5pay.html?order_sn=' . rawurlencode((string)$result['data']['order_sn']); return $result; } + public static function nativePaymentForCheck(array $data): array + { + $cents = (int)($data['total_price'] ?? 0); + if (empty($data['requires_payment']) || $cents <= 0 || !is_array($data['target_orders'] ?? null)) return []; + $target = null; + foreach ($data['target_orders'] as $item) { + if (is_array($item) && (int)($item['applicant_index'] ?? -1) === 0) { $target = $item; break; } + } + if ($target === null) $target = $data['target_orders'][0] ?? null; + $table = trim((string)($target['target_table'] ?? '')); + $orderSn = trim((string)($target['target_order_sn'] ?? '')); + if (!preg_match('/^[A-Za-z0-9_]+$/', $table) || !preg_match('/^[A-Za-z0-9_-]{1,100}$/', $orderSn)) return []; + return ['target_table' => $table, 'order_sn' => $orderSn, 'money' => intdiv($cents, 100) . '.' . str_pad((string)($cents % 100), 2, '0', STR_PAD_LEFT)]; + } public function payload(int $userId, array $params = []): array { $source = (string)($params['order_source'] ?? ''); unset($params['order_source']); return ['user_token' => authcode((string)json_encode(['purpose' => 'visa_api', 'user_id' => $userId, 'order_source' => $source], JSON_UNESCAPED_UNICODE), 'ENCODE', MINIAPI_TOKEN_KEY, 120)] + $params; } public function endpoint(string $userUrl, string $action): string { diff --git a/config/mini_payment.php b/config/mini_payment.php index 33c0e0c..c28a34e 100644 --- a/config/mini_payment.php +++ b/config/mini_payment.php @@ -53,5 +53,31 @@ return [ ], ], ], + 'visa' => [ + 'mini_pay' => [ + 'url' => '', + 'body' => '签证及入境服务费', + // 与公共网站的 PAY_MARK 保持一致,最终 mark 为此前缀加目标订单表名。 + 'mark_prefix' => 'bjglobal', + ], + 'companies' => [ + 'jn' => [ + 'mini_pay' => [], + ], + 'bj' => [ + 'mini_pay' => [], + ], + ], + 'apps' => [ + 'evus_jn_mp' => [ + 'company_code' => 'jn', + 'mini_pay' => [], + ], + 'evus_bj_mp' => [ + 'company_code' => 'bj', + 'mini_pay' => [], + ], + ], + ], ], ]; diff --git a/tests/visa_application_facade_self_check.php b/tests/visa_application_facade_self_check.php index 9abe1de..d64253b 100644 --- a/tests/visa_application_facade_self_check.php +++ b/tests/visa_application_facade_self_check.php @@ -20,6 +20,15 @@ if (!str_contains((string)file_get_contents(dirname(__DIR__) . '/app/miniapi/con $neutral = VisaApplicationService::decorateSubmitForCheck(['code' => 1, 'data' => ['order_sn' => 'V1', 'requires_payment' => false]]); $payable = VisaApplicationService::decorateSubmitForCheck(['code' => 1, 'data' => ['order_sn' => 'V 1', 'requires_payment' => true]]); if (isset($neutral['data']['pay_url']) || ($payable['data']['pay_url'] ?? '') !== 'https://www.qlevisa.com/index/pay/h5pay.html?order_sn=V%201') throw new RuntimeException('miniapi payment decoration failed'); +$native = VisaApplicationService::nativePaymentForCheck([ + 'requires_payment' => true, + 'total_price' => 7900, + 'target_orders' => [ + ['target_table' => 'singapore', 'target_order_sn' => '2026090412000012', 'applicant_index' => 0], + ], +]); +if ($native !== ['target_table' => 'singapore', 'order_sn' => '2026090412000012', 'money' => '79.00']) throw new RuntimeException('native payment target failed'); +if (VisaApplicationService::nativePaymentForCheck(['requires_payment' => true, 'total_price' => 7900, 'target_orders' => [['target_table' => 'bad-table', 'target_order_sn' => '1']]]) !== []) throw new RuntimeException('unsafe payment target accepted'); if (VisaApplicationService::dataSourceQueryForCheck('inac.city', ['model' => 'region', 'source_code' => 'jnwxmini', 'state_id' => '11', 'hotel_state' => '12', 'provinceCode' => '13', 'country' => 'CN', 'application_token' => 'secret', 'user_token' => 'secret']) !== ['type' => 'inac.city', 'model' => 'region', 'source_code' => 'jnwxmini', 'state_id' => '11', 'hotel_state' => '12', 'provinceCode' => '13', 'country' => 'CN']) throw new RuntimeException('miniapi data source filtering failed'); $controllerSource = (string)file_get_contents(dirname(__DIR__) . '/app/miniapi/controller/VisaApplication.php'); $routeSource = (string)file_get_contents(dirname(__DIR__) . '/app/miniapi/route/app.php');