100 lines
4.8 KiB
PHP
100 lines
4.8 KiB
PHP
<?php
|
|
namespace Tests\Feature\Payments;
|
|
|
|
use App\Http\Controllers\V1\Guest\PaymentController;
|
|
use App\Jobs\OrderHandleJob;
|
|
use App\Models\Order;
|
|
use App\Models\Payment;
|
|
use App\Services\Plugin\PluginManager;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Bus;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Mockery;
|
|
use Plugin\Epay\Plugin;
|
|
use Tests\TestCase;
|
|
|
|
require_once __DIR__ . '/../../../plugins-core/Epay/Plugin.php';
|
|
|
|
class EpayNotifyTest extends TestCase
|
|
{
|
|
private Payment $payment;
|
|
private Order $order;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config(['database.default'=>'sqlite','database.connections.sqlite.database'=>':memory:',
|
|
'cache.default'=>'array','cache.stores.redis'=>['driver'=>'array'],'queue.default'=>'sync']);
|
|
DB::purge('sqlite');
|
|
Cache::forgetDriver(['redis','array']);
|
|
$this->app->instance('hook.actions', []);
|
|
$this->app->instance('hook.filters', []);
|
|
Schema::create('v2_payment', function(Blueprint $t) {
|
|
$t->id();$t->string('uuid');$t->string('payment');$t->string('name');$t->text('config');
|
|
$t->boolean('enable');$t->string('notify_domain')->nullable();$t->integer('created_at')->nullable();$t->integer('updated_at')->nullable();
|
|
});
|
|
Schema::create('v2_order', function(Blueprint $t) {
|
|
$t->id();$t->string('trade_no');$t->integer('payment_id');$t->integer('total_amount');$t->integer('handling_amount')->nullable();
|
|
$t->integer('status');$t->string('callback_no')->nullable();$t->integer('paid_at')->nullable();$t->integer('created_at')->nullable();$t->integer('updated_at')->nullable();
|
|
});
|
|
$this->payment=Payment::create(['uuid'=>'isolated-gate','payment'=>'EPay','name'=>'Isolated EPay','enable'=>true,
|
|
'config'=>['pid'=>'isolated-merchant','key'=>'isolated-secret','type'=>'alipay','url'=>'https://example.test']]);
|
|
$this->order=Order::create(['trade_no'=>'isolated-order','payment_id'=>$this->payment->id,'total_amount'=>1000,'handling_amount'=>50,'status'=>Order::STATUS_PENDING]);
|
|
$plugin=new Plugin('epay');$plugin->boot();
|
|
$manager=Mockery::mock(PluginManager::class);
|
|
$manager->shouldReceive('getEnabledPaymentPlugins')->andReturn([$plugin]);
|
|
$this->app->instance(PluginManager::class,$manager);
|
|
Bus::fake([OrderHandleJob::class]);
|
|
Http::preventStrayRequests();
|
|
}
|
|
|
|
private function notify(array $changes=[]): mixed
|
|
{
|
|
$params=array_replace(['pid'=>'isolated-merchant','type'=>'alipay','money'=>'10.50','trade_status'=>'TRADE_SUCCESS',
|
|
'out_trade_no'=>'isolated-order','trade_no'=>'isolated-gateway-order','param'=>''],$changes);
|
|
ksort($params,SORT_STRING);$parts=[];
|
|
foreach($params as $k=>$v)if($v!==''&&$v!==null)$parts[]=$k.'='.$v;
|
|
$params['sign']=md5(implode('&',$parts).'isolated-secret');$params['sign_type']='MD5';
|
|
return (new PaymentController())->notify('EPay','isolated-gate',Request::create('/isolated-notify','GET',$params));
|
|
}
|
|
|
|
public function test_success_acknowledges_and_dispatches_fulfilment_only_once(): void
|
|
{
|
|
$this->assertSame('success',$this->notify());
|
|
$this->order->refresh();
|
|
$this->assertSame(Order::STATUS_PROCESSING,(int)$this->order->status);
|
|
$this->assertSame('isolated-gateway-order',$this->order->callback_no);
|
|
$this->assertNotNull($this->order->paid_at);
|
|
$this->assertSame('success',$this->notify());
|
|
Bus::assertDispatchedSync(OrderHandleJob::class,1);
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_signed_invalid_status_amount_and_channel_leave_order_unchanged(): void
|
|
{
|
|
foreach([['trade_status'=>'WAIT_BUYER_PAY'],['money'=>'10.00'],['pid'=>'other'],['type'=>'wxpay'],['out_trade_no'=>'unknown']] as $changes){
|
|
$response=$this->notify($changes);
|
|
$this->assertNotSame('success',$response);
|
|
$this->order->refresh();
|
|
$this->assertSame(Order::STATUS_PENDING,(int)$this->order->status);
|
|
$this->assertNull($this->order->paid_at);
|
|
$this->assertNull($this->order->callback_no);
|
|
}
|
|
Bus::assertNothingDispatched();
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_cancelled_order_does_not_dispatch_fulfilment(): void
|
|
{
|
|
$this->order->status=Order::STATUS_CANCELLED;$this->order->save();
|
|
$this->assertNotSame('success',$this->notify());
|
|
$this->assertSame(Order::STATUS_CANCELLED,(int)$this->order->fresh()->status);
|
|
Bus::assertNothingDispatched();
|
|
Http::assertNothingSent();
|
|
}
|
|
}
|