| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- <?php
- namespace Tests\Integration;
- use App\Services\Payment\JdPayService;
- use App\Services\Payment\NoPayService;
- use App\Services\Payment\PaymentProviderConfig;
- use App\Services\Payment\SanJinService;
- use App\Services\Payment\ZimuPayService;
- use Illuminate\Contracts\Encryption\DecryptException;
- use Illuminate\Encryption\Encrypter;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Facade;
- use RuntimeException;
- use Tests\Support\PaymentProviderTestCase;
- class PaymentProviderConfigTest extends PaymentProviderTestCase
- {
- public function test_unsaved_fields_fall_back_to_env_and_secrets_are_masked(): void
- {
- $result = $this->call('config', '/admin/paymentCompany/config', ['payment_company' => 'jd']);
- $this->assertSame(0, $result['code']);
- $fields = array_column($result['data']['fields'], null, 'field');
- $this->assertSame('env-jd-mch', $fields['mch_id']['value']);
- $this->assertSame('env', $fields['mch_id']['source']);
- $this->assertSame('', $fields['key']['value']);
- $this->assertTrue($fields['key']['has_value']);
- $this->assertSame('******1234', $fields['key']['masked']);
- $this->assertSame('empty', $fields['recharge_user_ids']['source']);
- $this->assertSame('env-jd-mch', JdPayService::getMerchantId());
- }
- public function test_saved_values_take_effect_immediately_and_blank_secret_keeps_existing(): void
- {
- $result = $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'jd', 'config' => [
- 'mch_id' => ' db-mch ', 'key' => 'db-secret-9999', 'pay_gateway' => 'https://db.example.com/pay',
- ]]);
- $this->assertSame(0, $result['code']);
- $this->assertSame('db-mch', JdPayService::getMerchantId());
- $this->assertSame('db-secret-9999', JdPayService::getSecret());
- $this->assertSame('https://db.example.com/pay', PaymentProviderConfig::get('jd', 'pay_gateway'));
- // 未提交字段继续回退 .env
- $fields = array_column((new PaymentProviderConfig())->show('jd')['fields'], null, 'field');
- $this->assertSame('admin', $fields['mch_id']['source']);
- $this->assertNotSame('admin', $fields['pay_query_gateway']['source']);
- $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'jd', 'config' => ['key' => '', 'mch_id' => 'db-mch-2']]);
- $this->assertSame('db-secret-9999', JdPayService::getSecret());
- $this->assertSame('db-mch-2', JdPayService::getMerchantId());
- $raw = DB::table('payment_providers')->where('code', 'jd')->value('config');
- $this->assertStringNotContainsString('db-secret-9999', $raw);
- $audit = DB::table('operation_audits')->where('resource_type', 'payment_company_config')->orderByDesc('id')->value('changes');
- $this->assertStringNotContainsString('db-secret-9999', $audit);
- $this->assertSame(2, DB::table('operation_audits')->where('resource_type', 'payment_company_config')->count());
- }
- public function test_recharge_whitelist_can_be_cleared_from_admin_to_allow_everyone(): void
- {
- $this->assertFalse(NoPayService::canUserRecharge(9));
- // 全角逗号不合法
- $this->assertNotSame(0, $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'no', 'config' => ['recharge_user_ids' => '9,10']])['code']);
- $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'no', 'config' => ['recharge_user_ids' => ' 9 , ,10']]);
- $this->assertSame(['9', '10'], PaymentProviderConfig::userIds('no'));
- $this->assertTrue(NoPayService::canUserRecharge(9));
- $this->assertFalse(NoPayService::canUserRecharge(7));
- $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'no', 'config' => ['recharge_user_ids' => '']]);
- $this->assertTrue(NoPayService::canUserRecharge(7));
- }
- public function test_invalid_input_is_rejected_without_writing(): void
- {
- $cases = [
- ['payment_company' => 'zimu', 'config' => ['pay_gateway' => 'not a url']],
- ['payment_company' => 'zimu', 'config' => ['recharge_user_ids' => 'abc']],
- ['payment_company' => 'zimu', 'config' => ['unknown' => 'x']],
- ['payment_company' => 'zimu', 'config' => ['app_id' => ['nested']]],
- ['payment_company' => 'nope', 'config' => ['app_id' => 'x']],
- ];
- foreach ($cases as $params) {
- $this->assertNotSame(0, $this->call('saveConfig', '/admin/paymentCompany/config/save', $params)['code']);
- }
- $this->assertNull(DB::table('payment_providers')->where('code', 'zimu')->value('config'));
- $this->assertSame('', ZimuPayService::getAppId());
- }
- public function test_sanjin_gateway_is_editable_with_builtin_default(): void
- {
- $this->assertSame('https://jkapi-sanjin.jkcbb.com/', (string)SanJinService::getClient()->getConfig('base_uri'));
- $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'sanjin', 'config' => ['gateway' => 'https://new.example.com/v1']]);
- $this->assertSame('https://new.example.com/v1/', (string)SanJinService::getClient()->getConfig('base_uri'));
- }
- public function test_unreadable_config_never_falls_back_to_env_or_opens_the_allowlist(): void
- {
- $config = new PaymentProviderConfig();
- $config->save('jd', ['mch_id' => 'active-merchant', 'key' => 'active-secret']);
- $config->save('no', ['recharge_user_ids' => '9']);
- $this->app->instance('encrypter', new Encrypter(str_repeat('x', 32), 'AES-256-CBC'));
- Facade::clearResolvedInstance('encrypter');
- foreach ([fn () => JdPayService::getMerchantId(), fn () => NoPayService::canUserRecharge(7)] as $read) {
- try {
- $read();
- $this->fail('An unreadable override must stop the operation.');
- } catch (RuntimeException $e) {
- $this->assertSame('支付商户配置读取失败,请联系管理员', $e->getMessage());
- $this->assertInstanceOf(DecryptException::class, $e->getPrevious());
- }
- }
- $response = $this->call('config', '/admin/paymentCompany/config', ['payment_company' => 'jd']);
- $this->assertSame(-3, $response['code']);
- $this->assertStringNotContainsString('MAC', $response['msg']);
- }
- public function test_database_read_failure_is_not_treated_as_unsaved_config(): void
- {
- DB::statement('DROP TABLE bot_payment_providers');
- $this->expectException(RuntimeException::class);
- $this->expectExceptionMessage('支付商户配置读取失败');
- PaymentProviderConfig::snapshot('jd');
- }
- public function test_pending_orders_block_credential_changes_for_all_providers_and_directions(): void
- {
- foreach ([['jd', 'mch_id', 'key', 'JDpay', 1], ['jd', 'mch_id', 'key', 'JD钱包', 2],
- ['no', 'deposit_mch_id', 'deposit_key', 'NOpay12', 1], ['no', 'withdraw_mch_id', 'withdraw_key', 'NOwithdraw', 2],
- ['zimu', 'app_id', 'key', '808pay', 1], ['zimu', 'app_id', 'key', 'ZIMUcash', 2],
- ['sanjin', 'mch_id', 'key', 'ylsm', 1], ['qianbao', 'merchant_id', 'secret', 'DF001', 2]] as [$code, $merchant, $secret, $channel, $type]) {
- DB::table('payment_orders')->delete();
- (new PaymentProviderConfig())->save($code, [$merchant => 'original', $secret => 'original-secret']);
- $audits = DB::table('operation_audits')->count();
- foreach ([0, 1, 4, 5] as $status) {
- DB::table('payment_orders')->delete();
- DB::table('payment_orders')->insert(compact('type', 'channel', 'status'));
- foreach ([$merchant, $secret] as $field) {
- $result = $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => $code, 'config' => [$field => 'changed']]);
- $this->assertSame(-3, $result['code']);
- $this->assertStringContainsString('存在未完成订单', $result['msg']);
- $this->assertSame('original', PaymentProviderConfig::get($code, $merchant));
- $this->assertSame('original-secret', PaymentProviderConfig::get($code, $secret));
- $this->assertSame($audits, DB::table('operation_audits')->count());
- }
- }
- }
- }
- public function test_rejected_merchant_change_preserves_a_valid_pending_order_callback(): void
- {
- $config = new PaymentProviderConfig();
- $config->save('jd', ['mch_id' => 'merchant-A', 'key' => 'secret-A']);
- $callback = ['userCode' => 'merchant-A', 'orderCode' => 'pending-order', 'amount' => '100.00', 'status' => '3'];
- $callback['sign'] = JdPayService::signature([$callback['orderCode'], $callback['amount'], $callback['userCode'], $callback['status']]);
- DB::table('payment_orders')->insert(['type' => 1, 'channel' => 'JDpay', 'status' => 1]);
- $result = $this->call('saveConfig', '/admin/paymentCompany/config/save', ['payment_company' => 'jd', 'config' => ['mch_id' => 'merchant-B', 'key' => 'secret-B']]);
- $this->assertSame(-3, $result['code']);
- $this->assertTrue(JdPayService::verifyPayNotify($callback));
- // 白名单、修正网关及原值保存不会中断已有订单。
- $config->save('jd', ['mch_id' => 'merchant-A', 'key' => '', 'recharge_user_ids' => '9', 'pay_gateway' => 'https://new.example.test/pay']);
- $this->assertTrue(JdPayService::verifyPayNotify($callback));
- DB::table('payment_orders')->update(['status' => 2]);
- $config->save('jd', ['mch_id' => 'merchant-B', 'key' => 'secret-B']);
- $this->assertSame('merchant-B', JdPayService::getMerchantId());
- }
- public function test_terminal_manual_and_other_provider_orders_do_not_block_updates(): void
- {
- foreach ([2, 3, 6] as $status) DB::table('payment_orders')->insert(['type' => 1, 'channel' => 'JDpay', 'status' => $status]);
- foreach ([3, 4] as $type) DB::table('payment_orders')->insert(['type' => $type, 'channel' => 'JDpay', 'status' => 0]);
- DB::table('payment_orders')->insert(['type' => 2, 'channel' => 'NOwithdraw', 'status' => 1]);
- (new PaymentProviderConfig())->save('jd', ['mch_id' => 'new-merchant', 'key' => 'new-secret']);
- $this->assertSame('new-merchant', JdPayService::getMerchantId());
- }
- }
|