[$code], ['jd', 'no', 'sanjin', 'qianbao', 'zimu'])); } #[DataProvider('providers')] #[RunInSeparateProcess] #[PreserveGlobalState(false)] public function test_each_payment_request_uses_one_snapshot_and_the_next_request_sees_new_config(string $code): void { // Replace only the transport; execute the real provider payload and signing code. $this->assertFalse(class_exists(\GuzzleHttp\Client::class, false)); class_alias(RecordingProviderClient::class, \GuzzleHttp\Client::class); $old = $new = []; foreach (PaymentProviderConfig::fields($code) as $field => $definition) { $old[$field] = match ($definition['type']) { 'url' => 'https://merchant-a.example.test/', 'user_ids' => '', 'secret' => 'secret-A', default => 'merchant-A', }; $new[$field] = match ($definition['type']) { 'url' => 'https://merchant-b.example.test/', 'user_ids' => '', 'secret' => 'secret-B', default => 'merchant-B', }; } $config = new PaymentProviderConfig(); $config->save($code, $old); config(['app.url' => 'https://local.example.test']); DB::connection()->setEventDispatcher(new Dispatcher(Facade::getFacadeApplication())); $switched = false; DB::listen(function ($event) use (&$switched, $config, $code, $new) { if (!$switched && str_starts_with($event->sql, 'select') && str_contains($event->sql, 'payment_providers')) { $switched = true; $config->save($code, $new); } }); $pay = match ($code) { 'jd' => fn () => JdPayService::pay('100', 'order'), 'no' => fn () => NoPayService::pay('100', 'order', 'member', 'NOpay12'), 'sanjin' => fn () => SanJinService::pay('100', 'order'), 'qianbao' => fn () => QianBaoService::payout('100', 'order', 'bank', 'account', 'card'), 'zimu' => fn () => ZimuPayService::pay('100', 'order', 'member'), }; $pay(); $this->assertTrue($switched); $this->assertRequestUses(RecordingProviderClient::$requests[0], $code, 'A'); $pay(); $this->assertRequestUses(RecordingProviderClient::$requests[1], $code, 'B'); } private function assertRequestUses(array $request, string $code, string $version): void { $this->assertSame('https://merchant-' . strtolower($version) . '.example.test/', $request['base_uri'] ?? $request['url']); $data = $request['options']['form_params'] ?? $request['options']['json']; $merchantField = match ($code) { 'jd' => 'userCode', 'sanjin' => 'mchId', 'qianbao' => 'merchantNum', default => 'appId' }; $this->assertSame('merchant-' . $version, $data[$merchantField]); $key = 'secret-' . $version; $expected = match ($code) { 'jd' => JdPayService::signature([$data['orderCode'], $data['amount'], $data['userCode']], $key), 'no' => NoPayService::signature($data, $key), 'sanjin' => SanJinService::signature(array_diff_key($data, ['sign' => true]), [], $key), 'qianbao' => md5($data['merchantNum'] . $data['orderNo'] . $data['amount'] . $key), 'zimu' => ZimuPayService::signature($data, $key), }; $this->assertSame($expected, $data['sign']); } }