@@ -21,9 +21,16 @@ class FakeInitiatePaymentGateway implements PaymentGatewayInterface
|
||||
{
|
||||
public static ?PaymentRequestData $lastRequest = null;
|
||||
|
||||
public static int $initiateCalls = 0;
|
||||
|
||||
public static int $verifyCalls = 0;
|
||||
|
||||
public static PaymentStatus $verifyStatus = PaymentStatus::Pending;
|
||||
|
||||
public function initiate(PaymentRequestData $data): PaymentResultData
|
||||
{
|
||||
self::$lastRequest = $data;
|
||||
self::$initiateCalls++;
|
||||
|
||||
return new PaymentResultData(
|
||||
status: PaymentStatus::Pending,
|
||||
@@ -34,7 +41,13 @@ class FakeInitiatePaymentGateway implements PaymentGatewayInterface
|
||||
|
||||
public function verify(string $gatewayTransactionId): PaymentResultData
|
||||
{
|
||||
throw new RuntimeException('not needed for this test');
|
||||
self::$verifyCalls++;
|
||||
|
||||
return new PaymentResultData(
|
||||
status: self::$verifyStatus,
|
||||
gatewayTransactionId: $gatewayTransactionId,
|
||||
gatewayPayload: ['trade_status' => self::$verifyStatus->value],
|
||||
);
|
||||
}
|
||||
|
||||
public function refund(string $gatewayTransactionId, string $amount, string $reason): RefundResultData
|
||||
@@ -53,6 +66,11 @@ beforeEach(function () {
|
||||
|
||||
app(PaymentGatewayFactory::class)->register(PaymentMethod::KbzMiniApp, FakeInitiatePaymentGateway::class);
|
||||
|
||||
FakeInitiatePaymentGateway::$lastRequest = null;
|
||||
FakeInitiatePaymentGateway::$initiateCalls = 0;
|
||||
FakeInitiatePaymentGateway::$verifyCalls = 0;
|
||||
FakeInitiatePaymentGateway::$verifyStatus = PaymentStatus::Pending;
|
||||
|
||||
$this->owner = User::factory()->create();
|
||||
$this->token = $this->owner->createToken('test-token')->plainTextToken;
|
||||
});
|
||||
@@ -79,6 +97,44 @@ test('the owner can initiate payment for their own pending_payment booking', fun
|
||||
->and($payment->gateway_transaction_id)->toBe("{$booking->booking_ref}-1");
|
||||
});
|
||||
|
||||
test('a repeat call while the previous attempt is still pending reuses it instead of precreating again', function () {
|
||||
$booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::PendingPayment]);
|
||||
FakeInitiatePaymentGateway::$verifyStatus = PaymentStatus::Pending;
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$this->token}")
|
||||
->postJson("/api/v1/payments/{$booking->booking_ref}/initiate")
|
||||
->assertCreated();
|
||||
|
||||
$firstPaymentId = Payment::where('booking_id', $booking->id)->sole()->id;
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$this->token}")
|
||||
->postJson("/api/v1/payments/{$booking->booking_ref}/initiate")
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.id', $firstPaymentId);
|
||||
|
||||
expect(Payment::where('booking_id', $booking->id)->count())->toBe(1)
|
||||
->and(FakeInitiatePaymentGateway::$initiateCalls)->toBe(1)
|
||||
->and(FakeInitiatePaymentGateway::$verifyCalls)->toBe(1);
|
||||
});
|
||||
|
||||
test('a repeat call reused attempt found completed on re-verify is returned without precreating again', function () {
|
||||
$booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::PendingPayment]);
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$this->token}")
|
||||
->postJson("/api/v1/payments/{$booking->booking_ref}/initiate")
|
||||
->assertCreated();
|
||||
|
||||
FakeInitiatePaymentGateway::$verifyStatus = PaymentStatus::Completed;
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$this->token}")
|
||||
->postJson("/api/v1/payments/{$booking->booking_ref}/initiate")
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.status', PaymentStatus::Completed->value);
|
||||
|
||||
expect(Payment::where('booking_id', $booking->id)->count())->toBe(1)
|
||||
->and(FakeInitiatePaymentGateway::$initiateCalls)->toBe(1);
|
||||
});
|
||||
|
||||
test('a retried payment attempt gets a unique merchant order id', function () {
|
||||
$booking = Booking::factory()->create(['user_id' => $this->owner->id, 'status' => BookingStatus::PendingPayment]);
|
||||
Payment::factory()->failed()->create(['booking_id' => $booking->id]);
|
||||
|
||||
Reference in New Issue
Block a user