diff --git a/README.md b/README.md index bc3fe7d..0a527b9 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,13 @@ Upon user confirmation of transaction, user is redirected to the appropriate pay When user is done with the transaction on the payment handler's end (either successfully paid, or declined transaction), user is redirected back to `/payment/completed` (`route('payment.finished.callback_url')` provided by this package) . -> If the `Payment` has [`metadata`](#step-1) (supplied with the payment initiation request), with a key named `completion_url`, the user will be redirected to that URL instead on successful payment, with the transaction reference included as `transaction_reference` in the URL query string. +> [!NOTE] If the `Payment` has [`metadata`](#step-1) (supplied with the payment initiation request), with a key named `completion_url`, the user will be redirected to that URL instead on successful payment, with the transaction reference included as `transaction_reference` in the URL query string. -> If there are additional steps you want to take upon successful payment, listen for `SuccessfulLaravelMultipayPaymentEvent`. It will be fired whenever a successful payment occurs, with its corresponding `Payment` model. +> [!NOTE] If the `Payment` has [`metadata`](#step-1) (supplied with the payment initiation request), with a key named `payment_processor`, you can use that to dynamically set the payment handler for that particular transaction. Valid value is any of [the providers listed above](#currently-supported-payment-handlers) + +> [!NOTE] If the `Payment` has [`metadata`](#step-1) (supplied with the payment initiation request), with a key named `split_code`, for Paystack transactions, it will be process as [Paystack Multi-split Transaction](https://paystack.com/docs/payments/multi-split-payments). + +> [!NOTE] If there are additional steps you want to take upon successful payment, listen for `SuccessfulLaravelMultipayPaymentEvent`. It will be fired whenever a successful payment occurs, with its corresponding `Payment` model. ## Payment Conflict Resolution (PCR) diff --git a/src/Services/PaymentHandlers/Paystack.php b/src/Services/PaymentHandlers/Paystack.php index 5e9f645..0d66609 100644 --- a/src/Services/PaymentHandlers/Paystack.php +++ b/src/Services/PaymentHandlers/Paystack.php @@ -11,6 +11,7 @@ use Damms005\LaravelMultipay\Contracts\PaymentHandlerInterface; use Damms005\LaravelMultipay\Webhooks\Contracts\WebhookHandler; use Damms005\LaravelMultipay\Exceptions\UnknownWebhookException; +use Illuminate\Support\Arr; class Paystack extends BasePaymentHandler implements PaymentHandlerInterface { @@ -149,16 +150,20 @@ protected function convertAmountToValueRequiredByPaystack($original_amount_displ protected function sendUserToPaymentGateway(string $redirect_or_callback_url, Payment $payment) { $paystack = app()->make(PaystackHelper::class, ['secret_key' => $this->secret_key]); + $payload = [ + 'email' => $payment->getPayerEmail(), + 'amount' => $this->convertAmountToValueRequiredByPaystack($payment->original_amount_displayed_to_user), + 'callback_url' => $redirect_or_callback_url, + ]; + + $splitCode = Arr::get($payment->metadata, 'split_code'); + if (boolval(trim($splitCode))) { + $payload['split_code'] = $splitCode; + } // the code below throws an exception if there was a problem completing the request, // else returns an object created from the json response - $trx = $paystack->transaction->initialize( - [ - 'email' => $payment->getPayerEmail(), - 'amount' => $this->convertAmountToValueRequiredByPaystack($payment->original_amount_displayed_to_user), - 'callback_url' => $redirect_or_callback_url, - ] - ); + $trx = $paystack->transaction->initialize($payload); // status should be true if there was a successful call if (!$trx->status) { diff --git a/tests/CompletePaymentFlowTest.php b/tests/CompletePaymentFlowTest.php index 32f53cd..12d0406 100644 --- a/tests/CompletePaymentFlowTest.php +++ b/tests/CompletePaymentFlowTest.php @@ -6,6 +6,7 @@ use Damms005\LaravelMultipay\Services\PaymentService; use Damms005\LaravelMultipay\Contracts\PaymentHandlerInterface; use Damms005\LaravelMultipay\Services\PaymentHandlers\BasePaymentHandler; +use Damms005\LaravelMultipay\Services\PaymentHandlers\Paystack; it('can confirm payment and send user to payment gateway', function () { $sampleInitialPayment = getSampleInitialPaymentRequest(); @@ -27,7 +28,7 @@ $mock = mock(BasePaymentHandler::class); $mock->makePartial(); - $mock->expects('proceedToPaymentGateway')->andReturnUsing(fn () => redirect()->away('nowhere')); + $mock->expects('proceedToPaymentGateway')->andReturnUsing(fn() => redirect()->away('nowhere')); return $mock; }); @@ -71,3 +72,24 @@ function () use ($payment) { ->assertSee($payment->transaction_reference) ->assertStatus(200); }); + +it('can use payment handler specified in the payload metadata', function () { + $payload1 = getSampleInitialPaymentRequest(); + + $this->post(route('payment.show_transaction_details_for_user_confirmation'), $payload1) + ->assertStatus(200); + + $payload2 = [ + ...$payload1, + 'payment_processor' => Paystack::getUniquePaymentHandlerName(), + ]; + + $this->post(route('payment.show_transaction_details_for_user_confirmation'), $payload2) + ->assertStatus(200); + + $payments = Payment::all(); + + expect($payments->count())->toEqual(2); + expect($payments->get(0)->payment_processor_name)->toEqual('Remita'); + expect($payments->get(1)->payment_processor_name)->toEqual('Paystack'); +}); diff --git a/tests/PaymentHandlers/Paystack.php b/tests/PaymentHandlers/PaystackTest.php similarity index 54% rename from tests/PaymentHandlers/Paystack.php rename to tests/PaymentHandlers/PaystackTest.php index 99ea14f..06b3db5 100644 --- a/tests/PaymentHandlers/Paystack.php +++ b/tests/PaymentHandlers/PaystackTest.php @@ -38,7 +38,7 @@ $paystackHelperMock->transaction = $transactionMock; - app()->bind(PaystackHelper::class, fn () => $paystackHelperMock); + app()->bind(PaystackHelper::class, fn() => $paystackHelperMock); (new Paystack())->proceedToPaymentGateway($this->payment, 'nowhere'); @@ -49,3 +49,41 @@ expect($response->getTargetUrl())->toBe('someplace-on-the-internet'); }); + +it('uses split code specified in metadata', function () { + /** + * @var Mock + */ + $paystackHelperMock = mock(PaystackHelper::class); + + /** + * @var Mock + */ + $transactionMock = mock('null'); + + $transactionMock + ->expects('initialize') + ->with([ + 'email' => 'user@gmail.com', + 'amount' => 50000, + 'callback_url' => 'far-away-land', + 'split_code' => 'spl_xxx-123', + ]) + ->andReturn((object)[ + 'status' => true, + 'data' => (object)[ + 'reference' => 'reference', + 'authorization_url' => 'someplace-on-the-internet', + ], + ]); + + $paystackHelperMock->transaction = $transactionMock; + + app()->bind(PaystackHelper::class, fn() => $paystackHelperMock); + + $this->payment->update([ + 'metadata' => ['split_code' => 'spl_xxx-123'], + ]); + + (new Paystack())->proceedToPaymentGateway($this->payment, 'far-away-land'); +})->only(); diff --git a/tests/Pest.php b/tests/Pest.php index c0ff286..d264a70 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -24,9 +24,9 @@ function doAuth() { - DB::statement('CREATE TABLE users ( id )'); + DB::statement('CREATE TABLE users ( id, email )'); - DB::table('users')->insert(['id' => 1]); + DB::table('users')->insert(['id' => 1, 'email' => 'user@gmail.com']); Auth::loginUsingId(1); }