'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);
$this->assertSame('新北中通快递有限公司', $signature->organization_name);
$this->assertSame('2026-09-21', $signature->payload['signature_date']);
$this->assertSame('courier', $signature->payload['partner_type']);
$this->assertSame('快递公司', $signature->partnerTypeLabel());
Storage::disk('uploads')->assertExists($signature->source_pdf_path);
$this->assertSame('新北中通快递有限公司', $signature->payload['account_proof_account_name']);
Http::assertSent(function (ClientRequest $request): bool {
if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') {
return false;
}
return data_get($request->data(), 'signers.0.signerType') === 1
&& data_get($request->data(), 'signers.0.orgSignerInfo.orgName') === '新北中通快递有限公司'
&& data_get($request->data(), 'signers.0.orgSignerInfo.transactorInfo.psnInfo.psnName') === '张经理';
});
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect();
Http::assertSentCount(4);
$this->assertSame(1, CourierAgreementSignature::query()->count());
}
public function test_supplier_requests_require_a_selected_supplier_before_sending(): void
{
Http::preventStrayRequests();
$this->withExceptionHandling();
$payload = $this->accountProofPayload('supplier-not-connected');
$payload['partner_type'] = 'supplier';
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)
->assertSessionHasErrors('supplier_id');
$this->assertSame(0, CourierAgreementSignature::query()->count());
Http::assertNothingSent();
}
public function test_supplier_request_uses_selected_supplier_and_its_signing_account(): void
{
$this->fakeEsign();
$repository = Mockery::mock(SupplierManagementMirrorRepository::class);
$repository->shouldReceive('signingSupplier')->with(7)->once()->andReturn((object) [
'Id' => 7, 'scmname' => '测试供应商', 'accountbankno' => '13900139000',
]);
$this->app->instance(SupplierManagementMirrorRepository::class, $repository);
$payload = $this->accountProofPayload('supplier-signing');
$payload['partner_type'] = 'supplier';
$payload['supplier_id'] = 7;
$payload['courier_name'] = '伪造名称';
unset($payload['esign_account']);
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect();
$signature = CourierAgreementSignature::query()->firstOrFail();
$this->assertSame('测试供应商', $signature->courier_name);
$this->assertSame('13900139000', $signature->operator_account);
$this->assertSame('supplier', $signature->payload['partner_type']);
$this->assertSame(7, $signature->payload['supplier_id']);
$this->assertSame(CourierAgreementSignature::STATUS_SIGNING, $signature->status);
}
public function test_invalid_supplier_and_courier_only_templates_never_send(): void
{
Http::preventStrayRequests();
$this->withExceptionHandling();
$repository = Mockery::mock(SupplierManagementMirrorRepository::class);
$repository->shouldReceive('signingSupplier')->with(999)->once()->andReturnNull();
$this->app->instance(SupplierManagementMirrorRepository::class, $repository);
$payload = $this->accountProofPayload('supplier-invalid');
$payload['partner_type'] = 'supplier';
$payload['supplier_id'] = 999;
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors('supplier_id');
$payload['document_type'] = 'quote-confirmation';
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors('document_type');
$this->assertSame(0, CourierAgreementSignature::query()->count());
Http::assertNothingSent();
}
public function test_supplier_cooperation_contract_without_a_template_never_sends(): void
{
Http::preventStrayRequests();
$this->withExceptionHandling();
$payload = $this->cooperationContractPayload('supplier-cooperation');
$payload['partner_type'] = 'supplier';
$payload['supplier_id'] = 7;
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)
->assertSessionHasErrors('supplier_contract_template');
$this->assertSame(0, CourierAgreementSignature::query()->count());
Http::assertNothingSent();
}
/** @dataProvider supplierTemplateProvider */
public function test_supplier_contract_keeps_the_selected_template_and_archives_the_signed_file(string $template): void
{
$this->fakeEsign();
$payload = $this->supplierContractPayload($template);
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect();
$signature = CourierAgreementSignature::query()->firstOrFail();
$metadata = SupplierCooperationTemplates::TEMPLATES[$template];
$this->assertSame($metadata['code'], $signature->template_code);
$this->assertSame('1', $signature->template_version);
$this->assertSame($metadata['file'], $signature->source_file_name);
$this->assertSame($metadata['name'], $signature->documentLabel());
$this->assertSame($template, $signature->payload['supplier_contract_template']);
$this->assertSame('SUP-TEST-001', $signature->payload['supplier_contract_fields']['number']);
$this->assertSame('张经理', $signature->payload['supplier_contract_signer_name']);
$source = Storage::disk('uploads')->get($signature->source_pdf_path);
$this->assertStringStartsWith('%PDF-', $source);
$this->assertSame(hash('sha256', $source), $signature->source_pdf_sha256);
Http::assertSent(function (ClientRequest $request): bool {
if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') return false;
$signers = $request->data()['signers'] ?? [];
return count($signers) === 2
&& data_get($signers, '0.orgSignerInfo.orgName') === $this->company->name
&& data_get($signers, '0.orgSignerInfo.transactorInfo.psnAccount') === '13900139000'
&& data_get($signers, '0.orgSignerInfo.transactorInfo.psnInfo.psnName') === '李经理'
&& data_get($signers, '1.orgSignerInfo.orgName') === '测试供应商有限公司'
&& data_get($signers, '1.orgSignerInfo.transactorInfo.psnAccount') === '13800138000'
&& data_get($signers, '1.orgSignerInfo.transactorInfo.psnInfo.psnName') === '张经理';
});
Http::swap(new \Illuminate\Http\Client\Factory());
Http::fake([
'https://smlopenapi.esign.test/v3/sign-flow/flow-agreement-001/detail' => Http::response(['code' => 0, 'data' => ['signFlowStatus' => 2]]),
'https://smlopenapi.esign.test/v3/sign-flow/flow-agreement-001/file-download-url' => Http::response(['code' => 0, 'data' => ['files' => [['downloadUrl' => 'https://download.esign.test/supplier-signed']]]]),
'https://download.esign.test/supplier-signed' => Http::response('%PDF-supplier-signed'),
]);
$archived = app(\App\Support\CourierAgreementSignatureService::class)->synchronize($signature, $this->user->id);
$this->assertSame(CourierAgreementSignature::STATUS_ARCHIVED, $archived->status);
$this->assertSame('%PDF-supplier-signed', Storage::disk('uploads')->get($archived->signed_pdf_path));
$this->assertSame($metadata['code'], $archived->template_code);
$this->assertSame($source, Storage::disk('uploads')->get($archived->source_pdf_path));
}
public static function supplierTemplateProvider(): array
{
return [['dropshipping'], ['consignment'], ['production']];
}
public function test_supplier_contract_rejects_invalid_template_and_missing_contract_identity(): void
{
Http::preventStrayRequests();
$this->withExceptionHandling();
$payload = $this->cooperationContractPayload('invalid-supplier-contract');
$payload += ['partner_type' => 'supplier', 'supplier_id' => 7, 'supplier_contract_template' => '../other'];
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors('supplier_contract_template');
$payload = $this->supplierContractPayload('dropshipping');
$payload['supplier_contract_fields']['number'] = '';
$payload['supplier_contract_fields']['location'] = '';
$this->post(route('business.courier-agreement-signatures.store'), $payload)->assertSessionHasErrors(['supplier_contract_fields.number', 'supplier_contract_fields.location']);
$this->assertSame(0, CourierAgreementSignature::query()->count());
Http::assertNothingSent();
}
public function test_supplier_contract_variables_are_escaped_and_cover_the_body_and_appendices(): void
{
$templates = app(SupplierCooperationTemplates::class);
foreach (array_keys(SupplierCooperationTemplates::TEMPLATES) as $template) {
$html = view('business.partials.supplier-cooperation-contract', [
'templateKey' => $template, 'mode' => 'preview',
'payload' => ['cooperation_party_a_name' => '', 'cooperation_party_b_name' => '新供应商', 'supplier_contract_fields' => ['number' => 'SUP-NEW-001']],
])->render();
$this->assertStringContainsString('<script>', $html);
$this->assertStringNotContainsString('