96 lines
4.4 KiB
PHP
96 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Payments;
|
|
|
|
use App\Models\Order;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Plugin\Epay\Plugin;
|
|
|
|
require_once __DIR__ . '/../../../plugins-core/Epay/Plugin.php';
|
|
|
|
class EpayCallbackTest extends TestCase
|
|
{
|
|
private function plugin(): Plugin
|
|
{
|
|
$plugin = new class('epay') extends Plugin {
|
|
public int $orderStatus = Order::STATUS_PENDING;
|
|
public ?string $callbackNo = null;
|
|
protected function findOrder(string $tradeNo): ?Order
|
|
{
|
|
if ($tradeNo !== 'test-order') return null;
|
|
$order = new Order();
|
|
$order->setRawAttributes(['payment_id' => 7, 'total_amount' => 1000, 'handling_amount' => 50, 'status' => $this->orderStatus, 'callback_no' => $this->callbackNo]);
|
|
return $order;
|
|
}
|
|
};
|
|
$plugin->setConfig(['id' => 7, 'pid' => 'test-merchant', 'key' => 'test-secret', 'type' => 'alipay', 'url' => 'https://example.test/']);
|
|
return $plugin;
|
|
}
|
|
|
|
private function signed(array $changes = []): array
|
|
{
|
|
$params = array_replace(['pid' => 'test-merchant', 'type' => 'alipay', 'out_trade_no' => 'test-order', 'trade_no' => 'gateway-order', 'money' => '10.50', 'trade_status' => 'TRADE_SUCCESS', 'param' => ''], $changes);
|
|
unset($params['sign'], $params['sign_type']);
|
|
ksort($params, SORT_STRING);
|
|
$parts = [];
|
|
foreach ($params as $k => $v) if ($v !== '' && $v !== null) $parts[] = $k . '=' . $v;
|
|
$params['sign'] = md5(implode('&', $parts) . 'test-secret');
|
|
$params['sign_type'] = 'MD5';
|
|
return $params;
|
|
}
|
|
|
|
public function test_accepts_successful_matching_order_including_handling_fee_and_empty_extension(): void
|
|
{
|
|
$this->assertSame(['trade_no' => 'test-order', 'callback_no' => 'gateway-order'], $this->plugin()->notify($this->signed()));
|
|
$this->assertIsArray($this->plugin()->notify($this->signed(['money' => '10.5'])));
|
|
}
|
|
|
|
public function test_signed_but_invalid_callbacks_cannot_mark_order_paid(): void
|
|
{
|
|
foreach ([['trade_status' => 'WAIT_BUYER_PAY'], ['money' => '10.00'], ['money' => '10.501'], ['money' => '1.05e1'], ['money' => '-10.50'], ['pid' => 'other'], ['type' => 'wxpay'], ['out_trade_no' => 'missing'], ['trade_no' => '']] as $change) {
|
|
$this->assertFalse($this->plugin()->notify($this->signed($change)), json_encode($change));
|
|
}
|
|
$plugin = $this->plugin();
|
|
$plugin->setConfig(['id' => 8, 'pid' => 'test-merchant', 'key' => 'test-secret', 'type' => 'alipay']);
|
|
$this->assertFalse($plugin->notify($this->signed()));
|
|
}
|
|
|
|
public function test_repeated_success_is_accepted_but_cancelled_or_conflicting_receipt_is_rejected(): void
|
|
{
|
|
$plugin = $this->plugin();
|
|
$plugin->orderStatus = Order::STATUS_COMPLETED;
|
|
$plugin->callbackNo = 'gateway-order';
|
|
$this->assertIsArray($plugin->notify($this->signed()));
|
|
$plugin->callbackNo = 'different-receipt';
|
|
$this->assertFalse($plugin->notify($this->signed()));
|
|
$plugin->callbackNo = null;
|
|
$plugin->orderStatus = Order::STATUS_CANCELLED;
|
|
$this->assertFalse($plugin->notify($this->signed()));
|
|
}
|
|
|
|
public function test_rejects_bad_missing_and_structured_signature_data(): void
|
|
{
|
|
$params = $this->signed();
|
|
$params['sign'] = str_repeat('0', 32);
|
|
$this->assertFalse($this->plugin()->notify($params));
|
|
unset($params['sign']);
|
|
$this->assertFalse($this->plugin()->notify($params));
|
|
$params = $this->signed();
|
|
$params['money'] = ['10.50'];
|
|
$this->assertFalse($this->plugin()->notify($params));
|
|
}
|
|
|
|
public function test_pay_signs_unescaped_values_and_normalizes_gateway_slash(): void
|
|
{
|
|
$result = $this->plugin()->pay(['total_amount' => 1050, 'trade_no' => 'test-order', 'notify_url' => 'https://example.test/notify?x=1&y=2', 'return_url' => 'https://example.test/#/order/test-order']);
|
|
$this->assertStringStartsWith('https://example.test/submit.php?', $result['data']);
|
|
parse_str(parse_url($result['data'], PHP_URL_QUERY), $params);
|
|
$sign = $params['sign'];
|
|
unset($params['sign'], $params['sign_type']);
|
|
ksort($params, SORT_STRING);
|
|
$parts = [];
|
|
foreach ($params as $k => $v) $parts[] = $k . '=' . $v;
|
|
$this->assertSame(md5(implode('&', $parts) . 'test-secret'), $sign);
|
|
}
|
|
}
|