Skip to content

Commit

Permalink
Add paystack split payment
Browse files Browse the repository at this point in the history
  • Loading branch information
damms005 committed Oct 16, 2024
1 parent 86d7091 commit 9eccc02
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 13 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
19 changes: 12 additions & 7 deletions src/Services/PaymentHandlers/Paystack.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 23 additions & 1 deletion tests/CompletePaymentFlowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
});
Expand Down Expand Up @@ -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');
});
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -49,3 +49,41 @@

expect($response->getTargetUrl())->toBe('someplace-on-the-internet');
});

it('uses split code specified in metadata', function () {
/**
* @var Mock<TObject>
*/
$paystackHelperMock = mock(PaystackHelper::class);

/**
* @var Mock<TObject>
*/
$transactionMock = mock('null');

$transactionMock
->expects('initialize')
->with([
'email' => '[email protected]',
'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();
4 changes: 2 additions & 2 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => '[email protected]']);

Auth::loginUsingId(1);
}
Expand Down

0 comments on commit 9eccc02

Please sign in to comment.