Skip to content

Commit 9eccc02

Browse files
committed
Add paystack split payment
1 parent 86d7091 commit 9eccc02

File tree

5 files changed

+82
-13
lines changed

5 files changed

+82
-13
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,13 @@ Upon user confirmation of transaction, user is redirected to the appropriate pay
127127
When user is done with the transaction on the payment handler's end (either successfully paid, or declined transaction), user is redirected
128128
back to `/payment/completed` (`route('payment.finished.callback_url')` provided by this package) .
129129

130-
> 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.
130+
> [!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.
131131
132-
> 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.
132+
> [!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)
133+
134+
> [!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).
135+
136+
> [!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.
133137
134138
## Payment Conflict Resolution (PCR)
135139

src/Services/PaymentHandlers/Paystack.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Damms005\LaravelMultipay\Contracts\PaymentHandlerInterface;
1212
use Damms005\LaravelMultipay\Webhooks\Contracts\WebhookHandler;
1313
use Damms005\LaravelMultipay\Exceptions\UnknownWebhookException;
14+
use Illuminate\Support\Arr;
1415

1516
class Paystack extends BasePaymentHandler implements PaymentHandlerInterface
1617
{
@@ -149,16 +150,20 @@ protected function convertAmountToValueRequiredByPaystack($original_amount_displ
149150
protected function sendUserToPaymentGateway(string $redirect_or_callback_url, Payment $payment)
150151
{
151152
$paystack = app()->make(PaystackHelper::class, ['secret_key' => $this->secret_key]);
153+
$payload = [
154+
'email' => $payment->getPayerEmail(),
155+
'amount' => $this->convertAmountToValueRequiredByPaystack($payment->original_amount_displayed_to_user),
156+
'callback_url' => $redirect_or_callback_url,
157+
];
158+
159+
$splitCode = Arr::get($payment->metadata, 'split_code');
160+
if (boolval(trim($splitCode))) {
161+
$payload['split_code'] = $splitCode;
162+
}
152163

153164
// the code below throws an exception if there was a problem completing the request,
154165
// else returns an object created from the json response
155-
$trx = $paystack->transaction->initialize(
156-
[
157-
'email' => $payment->getPayerEmail(),
158-
'amount' => $this->convertAmountToValueRequiredByPaystack($payment->original_amount_displayed_to_user),
159-
'callback_url' => $redirect_or_callback_url,
160-
]
161-
);
166+
$trx = $paystack->transaction->initialize($payload);
162167

163168
// status should be true if there was a successful call
164169
if (!$trx->status) {

tests/CompletePaymentFlowTest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Damms005\LaravelMultipay\Services\PaymentService;
77
use Damms005\LaravelMultipay\Contracts\PaymentHandlerInterface;
88
use Damms005\LaravelMultipay\Services\PaymentHandlers\BasePaymentHandler;
9+
use Damms005\LaravelMultipay\Services\PaymentHandlers\Paystack;
910

1011
it('can confirm payment and send user to payment gateway', function () {
1112
$sampleInitialPayment = getSampleInitialPaymentRequest();
@@ -27,7 +28,7 @@
2728
$mock = mock(BasePaymentHandler::class);
2829
$mock->makePartial();
2930

30-
$mock->expects('proceedToPaymentGateway')->andReturnUsing(fn () => redirect()->away('nowhere'));
31+
$mock->expects('proceedToPaymentGateway')->andReturnUsing(fn() => redirect()->away('nowhere'));
3132

3233
return $mock;
3334
});
@@ -71,3 +72,24 @@ function () use ($payment) {
7172
->assertSee($payment->transaction_reference)
7273
->assertStatus(200);
7374
});
75+
76+
it('can use payment handler specified in the payload metadata', function () {
77+
$payload1 = getSampleInitialPaymentRequest();
78+
79+
$this->post(route('payment.show_transaction_details_for_user_confirmation'), $payload1)
80+
->assertStatus(200);
81+
82+
$payload2 = [
83+
...$payload1,
84+
'payment_processor' => Paystack::getUniquePaymentHandlerName(),
85+
];
86+
87+
$this->post(route('payment.show_transaction_details_for_user_confirmation'), $payload2)
88+
->assertStatus(200);
89+
90+
$payments = Payment::all();
91+
92+
expect($payments->count())->toEqual(2);
93+
expect($payments->get(0)->payment_processor_name)->toEqual('Remita');
94+
expect($payments->get(1)->payment_processor_name)->toEqual('Paystack');
95+
});

tests/PaymentHandlers/Paystack.php renamed to tests/PaymentHandlers/PaystackTest.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
$paystackHelperMock->transaction = $transactionMock;
4040

41-
app()->bind(PaystackHelper::class, fn () => $paystackHelperMock);
41+
app()->bind(PaystackHelper::class, fn() => $paystackHelperMock);
4242

4343
(new Paystack())->proceedToPaymentGateway($this->payment, 'nowhere');
4444

@@ -49,3 +49,41 @@
4949

5050
expect($response->getTargetUrl())->toBe('someplace-on-the-internet');
5151
});
52+
53+
it('uses split code specified in metadata', function () {
54+
/**
55+
* @var Mock<TObject>
56+
*/
57+
$paystackHelperMock = mock(PaystackHelper::class);
58+
59+
/**
60+
* @var Mock<TObject>
61+
*/
62+
$transactionMock = mock('null');
63+
64+
$transactionMock
65+
->expects('initialize')
66+
->with([
67+
'email' => 'user@gmail.com',
68+
'amount' => 50000,
69+
'callback_url' => 'far-away-land',
70+
'split_code' => 'spl_xxx-123',
71+
])
72+
->andReturn((object)[
73+
'status' => true,
74+
'data' => (object)[
75+
'reference' => 'reference',
76+
'authorization_url' => 'someplace-on-the-internet',
77+
],
78+
]);
79+
80+
$paystackHelperMock->transaction = $transactionMock;
81+
82+
app()->bind(PaystackHelper::class, fn() => $paystackHelperMock);
83+
84+
$this->payment->update([
85+
'metadata' => ['split_code' => 'spl_xxx-123'],
86+
]);
87+
88+
(new Paystack())->proceedToPaymentGateway($this->payment, 'far-away-land');
89+
})->only();

tests/Pest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424

2525
function doAuth()
2626
{
27-
DB::statement('CREATE TABLE users ( id )');
27+
DB::statement('CREATE TABLE users ( id, email )');
2828

29-
DB::table('users')->insert(['id' => 1]);
29+
DB::table('users')->insert(['id' => 1, 'email' => 'user@gmail.com']);
3030

3131
Auth::loginUsingId(1);
3232
}

0 commit comments

Comments
 (0)