'sandbox', 'services.esign.base_url' => 'https://smlopenapi.esign.test', 'services.esign.app_id' => 'sandbox-app-id', 'services.esign.app_secret' => 'sandbox-app-secret', 'services.esign.notify_url' => null, ]); $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); } public function test_account_proof_uses_its_own_document_mapping_and_pushes_once(): void { $this->fakeEsign(); $payload = $this->accountProofPayload('request-001'); $this->loginAs() ->post(route('business.courier-agreement-signatures.store'), $payload) ->assertRedirect(route('business.pages.show', [ 'section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks', ])); $signature = CourierAgreementSignature::query()->firstOrFail(); $this->assertSame('TPL-ACCOUNT-CERT-V2', $signature->template_code); $this->assertSame(CourierAgreementSignature::STATUS_SIGNING, $signature->status); $this->assertSame('flow-agreement-001', $signature->esign_sign_flow_id); Storage::disk('uploads')->assertExists($signature->source_pdf_path); $this->assertSame('新北中通快递有限公司', $signature->payload['account_proof_account_name']); $this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect(); Http::assertSentCount(4); $this->assertSame(1, CourierAgreementSignature::query()->count()); } public function test_account_proof_accepts_private_account_holder_name_different_from_company_name(): void { $this->fakeEsign(); $payload = $this->accountProofPayload('request-private-holder'); $payload['account_proof_account_name'] = '李四'; $this->loginAs() ->post(route('business.courier-agreement-signatures.store'), $payload) ->assertRedirect(); $signature = CourierAgreementSignature::query()->firstOrFail(); $this->assertSame('李四', $signature->payload['account_proof_account_name']); $this->assertNotSame($this->company->name, $signature->payload['account_proof_account_name']); } public function test_cooperation_contract_maps_party_variables_and_creates_two_signers(): void { $this->fakeEsign(); $payload = [ 'idempotency_key' => 'cooperation-001', 'courier_name' => '新北中通', 'document_type' => 'cooperation-contract', 'signing_subject' => $this->company->name, 'company_full_name' => $this->company->name, 'signing_deadline' => now()->addDays(7)->toDateString(), 'esign_account' => '13800138000', 'secondary_esign_account' => '13900139000', 'operator_name' => '张经理', 'archive_mode' => 'auto', 'cooperation_party_a_name' => $this->company->name, 'cooperation_party_a_phone' => '0519-88886666', 'cooperation_party_b_name' => '新北中通快递有限公司', 'cooperation_party_b_phone' => '18112896651', 'cooperation_start_date' => '2026-01-01', 'cooperation_end_date' => '2026-12-31', ]; $this->loginAs() ->post(route('business.courier-agreement-signatures.store'), $payload) ->assertRedirect(); $signature = CourierAgreementSignature::query()->firstOrFail(); $this->assertSame('cooperation-contract', $signature->document_type); $this->assertSame('TPL-COOPERATION-CONTRACT-V1', $signature->template_code); $this->assertSame('18052733960', $signature->payload['secondary_esign_account']); $this->assertSame('0519-88886666', $signature->payload['cooperation_party_a_phone']); $this->assertSame('2026-12-31', $signature->payload['cooperation_end_date']); Http::assertSent(function (ClientRequest $request): bool { if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') { return false; } $json = $request->data(); $signers = $json['signers'] ?? []; return count($signers) === 2 && ($signers[0]['orgSignerInfo']['orgName'] ?? null) === $this->company->name && ($signers[0]['orgSignerInfo']['transactorInfo']['psnAccount'] ?? null) === '18052733960' && ($signers[1]['orgSignerInfo']['orgName'] ?? null) === '新北中通快递有限公司'; }); } public function test_verified_agreement_callback_is_idempotent_and_queues_archive(): void { Queue::fake(); $signature = CourierAgreementSignature::create([ 'company_id' => $this->company->id, 'business_no' => 'AGR-CALLBACK-001', 'idempotency_key' => 'callback-001', 'document_type' => 'account-proof', 'template_code' => 'TPL-ACCOUNT-CERT-V2', 'template_version' => '2', 'source_file_name' => '收款账户证明文件.doc', 'status' => CourierAgreementSignature::STATUS_SIGNING, 'courier_name' => '新北中通', 'company_full_name' => $this->company->name, 'organization_name' => '新北中通快递有限公司', 'operator_name' => '张经理', 'operator_account' => '13800138000', 'payload' => ['company_full_name' => $this->company->name, 'courier_name' => '新北中通'], 'environment' => 'sandbox', 'source_pdf_path' => 'courier-agreements/test/source.pdf', 'source_pdf_sha256' => hash('sha256', '%PDF-source'), 'esign_file_id' => 'file-agreement-001', 'esign_sign_flow_id' => 'flow-agreement-callback', 'remote_status' => '1', 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $payload = json_encode([ 'action' => 'SIGN_FLOW_COMPLETE', 'signFlowId' => 'flow-agreement-callback', 'signFlowStatus' => '2', ], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); $timestamp = (string) now()->getTimestampMs(); $callbackSignature = hash_hmac('sha256', $timestamp . '12' . $payload, 'sandbox-app-secret'); $server = [ 'HTTP_X_TSIGN_OPEN_TIMESTAMP' => $timestamp, 'HTTP_X_TSIGN_OPEN_SIGNATURE_ALGORITHM' => 'HmacSHA256', 'HTTP_X_TSIGN_OPEN_APP_ID' => 'sandbox-app-id', 'HTTP_X_TSIGN_OPEN_SIGNATURE' => $callbackSignature, 'CONTENT_TYPE' => 'application/json', ]; $this->call('POST', route('integrations.esign.callback') . '?zeta=2&alpha=1', [], [], [], $server, $payload) ->assertOk(); $signature->refresh(); $this->assertSame(CourierAgreementSignature::STATUS_COMPLETED, $signature->status); $this->assertSame(hash('sha256', $payload), $signature->callback_payload_hash); Queue::assertPushed(ArchiveCourierAgreementSignature::class, 1); $this->assertNull(app(\App\Support\CourierAgreementSignatureService::class)->acceptCallback( 'flow-agreement-callback', '2', hash('sha256', $payload), )); $this->call('POST', route('integrations.esign.callback') . '?zeta=2&alpha=1', [], [], [], $server, $payload) ->assertOk(); Queue::assertPushed(ArchiveCourierAgreementSignature::class, 1); } private function fakeEsign(): void { Http::fake(function (ClientRequest $request) { return match (true) { $request->url() === 'https://smlopenapi.esign.test/v3/files/file-upload-url' => Http::response([ 'code' => 0, 'data' => ['fileId' => 'file-agreement-001', 'fileUploadUrl' => 'https://upload.esign.test/agreement-001'], ]), $request->url() === 'https://upload.esign.test/agreement-001' => Http::response(['ok' => true]), $request->url() === 'https://smlopenapi.esign.test/v3/files/file-agreement-001' => Http::response([ 'code' => 0, 'data' => ['status' => 2], ]), $request->url() === 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file' => Http::response([ 'code' => 0, 'data' => ['signFlowId' => 'flow-agreement-001'], ]), default => Http::response(['code' => 404], 404), }; }); } private function accountProofPayload(string $idempotencyKey): array { return [ 'idempotency_key' => $idempotencyKey, 'courier_name' => '新北中通', 'document_type' => 'account-proof', 'signing_subject' => $this->company->name, 'company_full_name' => $this->company->name, 'signing_deadline' => now()->addDays(7)->toDateString(), 'esign_account' => '13800138000', 'operator_name' => '张经理', 'archive_mode' => 'auto', 'account_proof_effective_date' => now()->toDateString(), 'account_proof_account_name' => '新北中通快递有限公司', 'account_proof_bank_name' => '中国工商银行', 'account_proof_bank_account' => '6222000012345678', ]; } }