0,'msg'=>'temporary failure']; function requestForTarget(...$args) { self::$calls++; if (self::$throws) throw new \RuntimeException('offline'); return self::$result; } } } namespace app\miniapi\controller { class Base { public array $input = ['openid'=>'test-openid']; public string $appCode = 'evus_jn_mp'; protected function requireLogin() { return 7; } protected function postData() { return $this->input; } protected function currentMiniAppCode() { return $this->appCode; } protected function currentMiniAppContext($business) { return new \stdClass(); } protected function ok($data, $msg = '') { return ['code'=>1,'data'=>$data,'msg'=>$msg]; } protected function fail($msg, $code = 0, $data = []) { return ['code'=>$code,'msg'=>$msg,'data'=>$data]; } } } namespace { require __DIR__ . '/../app/miniapi/service/VisaApplication/VisaApplicationService.php'; require __DIR__ . '/../app/miniapi/controller/VisaApplication.php'; function checkSubmit($condition, $message) { if (!$condition) throw new RuntimeException($message); } $controller = new app\miniapi\controller\VisaApplication(); $data = ['order_sn'=>'TEST1','requires_payment'=>true,'total_price'=>7900,'payment_ready'=>true,'payment_business'=>'arrival_card','target_orders'=>[['target_table'=>'singapore','target_order_sn'=>'TEST1','applicant_index'=>0]]]; api\Httpcurl::$result = ['code'=>1,'data'=>$data]; $result = $controller->submit(); checkSubmit($result['code'] === 1 && $result['data']['submission_status'] === 'submitted' && !isset($result['data']['pay']), 'parameter failure must not undo submission'); app\miniapi\service\Payment\MiniPayService::$throws = true; checkSubmit($controller->submit()['code'] === 1, 'transport exception must not undo submission'); app\miniapi\service\Payment\MiniPayService::$throws = false; $data['target_orders'][] = ['target_table'=>'singapore','target_order_sn'=>'TEST2','applicant_index'=>1]; api\Httpcurl::$result['data'] = $data; $before = app\miniapi\service\Payment\MiniPayService::$calls; checkSubmit($controller->submit()['code'] === 1 && app\miniapi\service\Payment\MiniPayService::$calls === $before, 'batch must not auto-charge first applicant'); api\Httpcurl::$result = ['code'=>0,'msg'=>'validation failed']; checkSubmit($controller->submit()['code'] === 0, 'actual submission failure remains a failure'); api\Httpcurl::$result = ['code'=>0,'msg'=>'validation failed','data'=>['field_errors'=>[['module_key'=>'traveler','field'=>'passport_number','path'=>'data.applicants.0.traveler.passport_number','message'=>'护照号格式不正确','value'=>'SECRET','money'=>'0.01','mark'=>'forged','target_table'=>'secret']],'target_orders'=>[['target_table'=>'secret']],'pay_url'=>'https://secret.test']]; checkSubmit(($controller->submit()['data'] ?? []) === [], 'legacy submit rejection must not expose direct field metadata'); $controller->appCode = 'evus_jn_app'; $controller->input = ['request_id'=>str_repeat('d',64),'service_code'=>'TEST']; api\Httpcurl::$result = ['code'=>1,'data'=>['order_sn'=>'TEST1','requires_payment'=>true,'total_price'=>7900,'payment_ready'=>true,'payment_business'=>'arrival_card','target_orders'=>[['target_table'=>'singapore','target_order_sn'=>'TEST1','applicant_index'=>0]],'submission_status'=>'submitted', 'pay_url'=>'https://secret.test']]; $androidStatus = $controller->directStatus(); checkSubmit(($androidStatus['data']['app_payment'] ?? null) === ['order_sn'=>'TEST1','money'=>'79.00','mark'=>'evuscnsingapore','business'=>'arrival_card'] && !isset($androidStatus['data']['target_orders'], $androidStatus['data']['pay_url']), 'android direct status must sanitize internal targets and expose trusted payment business'); $androidForm = $controller->directForm(); checkSubmit(($androidForm['data']['app_payment']['business'] ?? '') === 'arrival_card' && !isset($androidForm['data']['target_orders'], $androidForm['data']['pay_url']), 'android existing direct form result uses the same safe payment shape'); api\Httpcurl::$result = ['code'=>1,'data'=>['order_sn'=>'TEST1','requires_payment'=>true,'total_price'=>7900,'payment_ready'=>false,'directory_synced'=>true,'payment_business'=>'arrival_card','target_orders'=>[['target_table'=>'singapore','target_order_sn'=>'TEST1','applicant_index'=>0]]]]; checkSubmit(!isset($controller->directStatus()['data']['app_payment']), 'android status must not create payment before directory payment readiness'); api\Httpcurl::$result = ['code'=>0,'msg'=>'validation failed','data'=>['field_errors'=>[['module_key'=>'traveler','field'=>'passport_number','path'=>'data.applicants.0.traveler.passport_number','message'=>'护照号格式不正确','value'=>'SECRET','money'=>'0.01','mark'=>'forged','target_table'=>'secret']],'target_orders'=>[['target_table'=>'secret']],'pay_url'=>'https://secret.test']]; $controller->input = ['request_id'=>str_repeat('f',64),'service_code'=>'TEST','nationality_iso2'=>'CN','destination_iso2'=>'SG']; $safeFailure = $controller->directSubmit(); checkSubmit($safeFailure['code'] === 0 && ($safeFailure['data']['field_errors'] ?? null) === [['module_key'=>'traveler','field_id'=>'passport_number','path'=>'data.applicants.0.traveler.passport_number','message'=>'护照号格式不正确']], 'direct rejection forwards only bounded field location metadata'); checkSubmit(!str_contains(json_encode($safeFailure['data']), 'SECRET') && !str_contains(json_encode($safeFailure['data']), 'forged') && !str_contains(json_encode($safeFailure['data']), 'secret'), 'direct rejection must drop sensitive upstream keys and values'); $controller->appCode = 'evus_jn_mp'; $controller->input = ['openid'=>'test-openid']; api\Httpcurl::$result = ['code'=>1,'data'=>$data]; $controller->input += ['business'=>'singapore','order_sn'=>'TEST1','total_price'=>1,'target_table'=>'evus']; api\Httpcurl::$result = ['code'=>1,'data'=>array_merge($data, ['target_orders'=>[$data['target_orders'][0]]])]; app\miniapi\service\Payment\MiniPayService::$result = ['code'=>1,'data'=>['package'=>'prepay_id=test']]; checkSubmit($controller->orderPayment()['code'] === 1, 'per-person payment parameters are forwarded'); checkSubmit(!isset(api\Httpcurl::$lastPayload['total_price'], api\Httpcurl::$lastPayload['target_table']), 'client cannot choose amount or target table'); $controller->input = ['openid'=>'test-openid','request_id'=>str_repeat('a',64),'service_code'=>'TEST','nationality_iso2'=>'CN','destination_iso2'=>'SG']; checkSubmit($controller->directSubmit()['code'] === 1, 'new direct submit still obtains original native payment parameters'); checkSubmit(api\Httpcurl::$lastPayload['request_id'] === str_repeat('a',64) && !isset(api\Httpcurl::$lastPayload['application_token'],api\Httpcurl::$lastPayload['submission_token']), 'direct submit needs no application credentials'); $claims=json_decode(api\Httpcurl::$lastPayload['user_token'],true); checkSubmit($claims['order_source']==='jnwxmini' && $claims['user_id']===7, 'new endpoint forwards verified account and source'); $controller->appCode = 'evus_jn_app'; $controller->input = ['request_id'=>str_repeat('b',64),'service_code'=>'TEST','nationality_iso2'=>'CN','destination_iso2'=>'SG','mark'=>'forged','money'=>'0.01','target_table'=>'evus']; $androidSubmit = $controller->directSubmit(); checkSubmit(($androidSubmit['data']['app_payment'] ?? null) === ['order_sn'=>'TEST1','money'=>'79.00','mark'=>'evuscnsingapore','business'=>'arrival_card'] && !isset($androidSubmit['data']['pay']), 'android direct submit must expose only the server-derived app payment target and business'); $controller->input = ['business'=>'singapore','order_sn'=>'TEST1','mark'=>'forged','money'=>'0.01','target_table'=>'evus']; $androidRepay = $controller->orderPayment(); checkSubmit(($androidRepay['data']['app_payment'] ?? null) === ['order_sn'=>'TEST1','money'=>'79.00','mark'=>'evuscnsingapore','business'=>'singapore'] && !isset($androidRepay['data']['pay']), 'android repeat payment must only echo the upstream-verified public business'); echo "PASS visa_submission_payment_self_check\n"; }