| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- <?php
- namespace Tests\Integration;
- use App\Services\Payment\JdPayService;
- use App\Services\Payment\NoPayService;
- use App\Services\Payment\PaymentProviderConfig;
- use App\Services\Payment\QianBaoService;
- use App\Services\Payment\SanJinService;
- use App\Services\Payment\ZimuPayService;
- use Illuminate\Events\Dispatcher;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Facade;
- use PHPUnit\Framework\Attributes\DataProvider;
- use PHPUnit\Framework\Attributes\PreserveGlobalState;
- use PHPUnit\Framework\Attributes\RunInSeparateProcess;
- use Tests\Support\RecordingProviderClient;
- class PaymentProviderSnapshotTest extends \Tests\Support\PaymentProviderTestCase
- {
- public static function providers(): array
- {
- return array_combine(['jd', 'no', 'sanjin', 'qianbao', 'zimu'], array_map(fn ($code) => [$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']);
- }
- }
|