This commit is contained in:
gaofeng
2026-09-04 14:25:08 +08:00
parent 4150f64d3b
commit 9ea99da9c6
6 changed files with 99 additions and 1 deletions

View File

@@ -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', '')));

View File

@@ -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,

View File

@@ -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'] ?? '')),
];
}

View File

@@ -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
{

View File

@@ -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' => [],
],
],
],
],
];

View File

@@ -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');