Skip to content

Commit

Permalink
Merge pull request #21 from njoguamos/remove-DTOs
Browse files Browse the repository at this point in the history
Remove dtos
  • Loading branch information
njoguamos authored Mar 19, 2024
2 parents 67e6b8c + 847aefa commit 487aae1
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
php: [8.3, 8.2, 8.1]
laravel: [10.*, 11.*]
stability: [prefer-stable, prefer-lowest]
stability: [prefer-stable]
include:
- laravel: 10.*
testbench: 8.*
Expand Down
136 changes: 89 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,13 @@ You can also call the `createToken' in `Pesapal` class directly to generate the
use NjoguAmos\Pesapal\Pesapal;

$token = Pesapal::createToken();

// $token in an Instance of PesapalToken Eloquent Model
$data = $token->toArray();
````

Sample output
The results of `createToken` is an instance of `PesapalToken` Eloquent Model. Which mean you can call Eloquent methods e.g.

```php
$data = $token->toArray();
```

```php
[
Expand All @@ -124,12 +125,13 @@ $ipn = Pesapal::createIpn(
url: 'https://www.yourapp.com/ipn',
ipnType: IpnType::GET,
);

// $ipn is an Instance of PesapalIpn Eloquent Model
$data = $ipn->toArray();
````

Sample output
The results of `createIpn` is an instance of `PesapalIpn` Eloquent Model. Which mean you can call Eloquent methods e.g.

```php
$data = $ipn->toArray();
```

```php
[
Expand All @@ -151,7 +153,7 @@ You can go ahead and use the `ipn_id` to submit a Submit Order Requests.

There are two ways to get the registered IPNs.

1. You can use the `getIpns` method in the `Pesapal` class to get a IPN from Pesapal API.
1. You can use the `getIpns` method in the `Pesapal` class to get a IPN from Pesapal API. This method returns an array for successful response or an instance of [Saloon Response](https://docs.saloon.dev/the-basics/responses) for failed response.

```php
use NjoguAmos\Pesapal\Pesapal;
Expand All @@ -160,22 +162,23 @@ use NjoguAmos\Pesapal\Pesapal;
$response = Pesapal::getIpns();
```

```json
Sample successful response
```php
[
{
"url": "https://www.myapplication.com/ipn",
"created_date": "2022-03-03T17:29:03.7208266Z",
"ipn_id": "e32182ca-0983-4fa0-91bc-c3bb813ba750",
"error": null,
"status": "200"
},
{
"url": "https://ipn.myapplication.com/application2",
"created_date": "2021-12-05T04:23:45.5509243Z",
"ipn_id": "c3bb813ba750-0983-4fa0-91bc-e32182ca",
"error": null,
"status": "200"
}
[
"url" => "https://www.myapplication.com/ipn",
"created_date" => "2022-03-03T17:29:03.7208266Z",
"ipn_id" => "e32182ca-0983-4fa0-91bc-c3bb813ba750",
"error" => null,
"status" => "200"
],
[
"url"=> "https://ipn.myapplication.com/application2",
"created_date"=> "2021-12-05T04:23:45.5509243Z",
"ipn_id"=> "c3bb813ba750-0983-4fa0-91bc-e32182ca",
"error"=> null,
"status"=> "200"
]
]
```

Expand Down Expand Up @@ -213,7 +216,7 @@ $ips = PesapalIpn::all();

### 4. Submit Order Request Endpoint

To submit an order request, you can use the `createOrder` method in the `Pesapal` class. You will need to construct a DTO for `PesapalOrderData` and `PesapalAddressData` as shown below.
To submit an order request, you can use the `createOrder` method in the `Pesapal` class. You will need to construct a DTO for `PesapalOrderData` and `PesapalAddressData` as shown below. This method returns an array for successful response or an instance of [Saloon Response](https://docs.saloon.dev/the-basics/responses) for failed response.

> **info** You must provide a registered `PesapalIpn`.
Expand Down Expand Up @@ -261,7 +264,7 @@ $order = Pesapal::createOrder(
);
```

If the response was successful, your response should be as follows.
Sample successful response

```php
[
Expand All @@ -278,7 +281,7 @@ You can now re-direct the user to the `redirect_url` to complete the payment.

### 5. Get Transaction Status Endpoint

You can check the status of a transaction using `OrderTrackingId` issued when creating an order.
You can check the status of a transaction using `OrderTrackingId` issued when creating an order. You can do so by using the `getTransactionStatus` method in the `Pesapal` class. This method returns an array for successful response or an instance of [Saloon Response](https://docs.saloon.dev/the-basics/responses) for failed response.

```php
use NjoguAmos\Pesapal\Pesapal;
Expand All @@ -287,35 +290,74 @@ use NjoguAmos\Pesapal\Pesapal;
orderTrackingId: 'b945e4af-80a5-4ec1-8706-e03f8332fb04',
);

// $transaction is an instance of TransactionStatusResponse DTO
// $transaction either an array or an instance of Saloon Response
```

You should be able to get response details for the `$transaction` DTO

| `$transaction` Property | Description | Sample response |
|------------------------------|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| `payment_method` | payment method used | `Visa`, `MPESA`, `""` |
| `amount` | order amount | `1200` |
| `created_date` | payment date in `UTC` | `2022-04-30T07:41:09.763` |
| `confirmation_code` | payment provider code | `SCI5FB84RD` |
| `payment_status_description` | transaction status | `INVALID`, `FAILED`, `COMPLETED` or `REVERSED`. |
| `description` | payment status description | `"Unable to Authorize Transaction.Kindly contact your bank for assistance"`. |
| `message` | whether transaction was processed successfully or not. | `"Request processed successfully"`. |
| `payment_account` | Masked card number or phone number used during payment | `0700*****8`, `"` |
| `call_back_url` | Payment redirect url | `https://test.com/?OrderTrackingId=7e6b62d9-883e-440f-a63e-e1105bbfadc3&OrderMerchantReference=1515111111` |
| `status_code` | Payment status description | `0` - INVALID, `1` - COMPLETED, `2` - FAILED or `3` - REVERSED |
| `merchant_reference` | Unique ID provided during SubmitOrderRequest | `1515111111`, `7e6b62d9-883e-440f-a63e-e1105bbfadc` |
| `currency` | Transaction currency | `KES`, `USD` |
| `status` | HTTP status code | `200`, `500` |
| `error` | An error object | `{ "error_type": 'api_error, "code": 'payment_details_not_found, "message": 'Pending Payment, "call_back_url": null }` |

Sample successful response
```php
[
"payment_method" => "MpesaKE"
"amount" => 6.0
"created_date" => "2024-03-19T20:08:46.39"
"confirmation_code" => "SCJ8JQ26SW"
"order_tracking_id" => "af2234da-03ee-4b60-b2dd-dd746bcda1bd"
"payment_status_description" => "Completed"
"description" => null
"message" => "Request processed successfully"
"payment_account" => "2547xxx56689"
"call_back_url" => "http://127.0.0.1:8000/pesapal-callback?OrderTrackingId=af2234da-03ee-4b60-b2dd-dd746bcda1bd&OrderMerchantReference=1"
"status_code" => 1
"merchant_reference" => "1"
"payment_status_code" => ""
"currency" => "KES"
"error" => [
"error_type" => null
"code" => null
"message" => null
]
"status" => "200"
]
```

### 6. Recurring / Subscription Based Payments
- [ ] TODO: Add documentation for recurring payments

### 7. Refund Request
- [ ] TODO: Add documentation for refund request


## A note about responses

For flexibility and simplicity, the `Pesapal` static method returns an `array` for successful responses or an instance of `Saloon Response` for failed responses.

Example, when getting the transaction status using `getTransactionStatus` will either return an array of transaction details or an instance of `Saloon Response` if the request was not successful.

```php
use NjoguAmos\Pesapal\Pesapal;

$transaction = Pesapal::getTransactionStatus(
orderTrackingId: 'b945e4af-80a5-4ec1-8706-e03f8332fb04',
);

if (is_array($transaction)) {
// The API call was successful and response is an array
// [
// "payment_method" => "MpesaKE"
// "amount" => 6.0
// "created_date" => "2024-03-19T20:08:46.39"
// "confirmation_code" => "SCJ8JQ26SW"
// "....more field"
// ]
} else {
// The API call was not successful. The response is an instance of Saloon Response
// $transaction->status() ---> response status code.
// $transaction->headers() ---> Returns all response headers
// $transaction->getPendingRequest() ---> PendingRequest class that was built up for the request.
}
```

You can learn more about the [Saloon Response(https://docs.saloon.dev/the-basics/responses). You can use the response to diagnose the issue with the request.

## Testing

> **Info** Where possible, the tests uses real [sandbox credentials](https://developer.pesapal.com/api3-demo-keys.txt), and as such the request is not mocked. The resulting response is saved at `tests/Fixtures` and used in future tests. Where it is impossible to use real credentials, the request is mocked. You can recreate the fixtures by deleting `tests/Fixtures` and running the tests.
Expand Down
7 changes: 5 additions & 2 deletions src/Pesapal.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,17 @@ public static function createOrder(PesapalOrderData $orderData, PesapalAddressDa
/**
* @throws FatalRequestException
* @throws RequestException
* @throws JsonException
*/
public static function getTransactionStatus(string $orderTrackingId)
public static function getTransactionStatus(string $orderTrackingId): array | Response
{
$connector = new PesapalConnector();
$request = new GetPesapalTransactionStatus(orderTrackingId: $orderTrackingId);

$response = $connector->send($request);

return $response->dtoOrFail();


return $response->ok() ? $response->array() : $response;
}
}
34 changes: 0 additions & 34 deletions src/Requests/GetPesapalTransactionStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

namespace NjoguAmos\Pesapal\Requests;

use JsonException;
use NjoguAmos\Pesapal\DTOs\TransactionError;
use NjoguAmos\Pesapal\DTOs\TransactionStatusResponse;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;

class GetPesapalTransactionStatus extends Request
{
Expand All @@ -28,34 +24,4 @@ protected function defaultQuery(): array
'orderTrackingId' => $this->orderTrackingId
];
}

/**
* @throws JsonException
*/
public function createDtoFromResponse(Response $response): TransactionStatusResponse
{
$data = $response->json();

return new TransactionStatusResponse(
amount: $data['amount'],
created_date: $data['created_date'],
payment_status_description: $data['payment_status_description'],
call_back_url: $data['call_back_url'],
message: $data['message'],
merchant_reference: $data['merchant_reference'],
currency: $data['currency'],
error: $data['error'] ? new TransactionError(
error_type: $data['error']['error_type'],
code: $data['error']['code'],
message: $data['error']['message'],
call_back_url: $data['error']['call_back_url'],
) : null,
status_code: $data['status_code'],
status: $data['status'],
payment_method: $data['payment_method'],
description: $data['description'],
confirmation_code: $data['confirmation_code'],
payment_account: $data['payment_account']
);
}
}
35 changes: 17 additions & 18 deletions tests/PesapalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,22 @@
orderTrackingId: $trackingId,
);

expect(value: $transaction->amount)->toBe(expected: $amount)
->and(value: $transaction->created_date)->not->toBeEmpty()
->and(value: $transaction->payment_status_description)->toBe(expected: TransactionStatusCode::INVALID->name)
->and(value: $transaction->call_back_url)->not->toBeEmpty()
->and(value: $transaction->error->call_back_url)->not->toBeEmpty()
->and(value: $transaction->message)->toBe(expected: "Request processed successfully")
->and(value: $transaction->merchant_reference)->not->toBeEmpty()
->and(value: $transaction->currency)->toBe(expected: ISOCurrencyCode::KES->name)
->and(value: $transaction->status_code)->toBe(expected: TransactionStatusCode::INVALID->value)
->and(value: $transaction->status)->toBe(expected: TransactionStatus::INCOMPLETE->value)
->and(value: $transaction->payment_method)->toBeEmpty()
->and(value: $transaction->description)->toBeEmpty()
->and(value: $transaction->confirmation_code)->toBeEmpty()
->and(value: $transaction->payment_account)->toBeEmpty()
->and(value: $transaction->payment_status_code)->toBeEmpty()
->and(value: $transaction->error->error_type)->toBe(expected: 'api_error')
->and(value: $transaction->error->code)->toBe(expected: 'payment_details_not_found')
->and(value: $transaction->error->message)->toBe(expected: 'Pending Payment');
expect(value: $transaction['amount'])->toBe(expected: $amount)
->and(value: $transaction['created_date'])->not->toBeEmpty()
->and(value: $transaction['payment_status_description'])->toBe(expected: TransactionStatusCode::INVALID->name)
->and(value: $transaction['call_back_url'])->not->toBeEmpty()
->and(value: $transaction['message'])->toBe(expected: "Request processed successfully")
->and(value: $transaction['merchant_reference'])->not->toBeEmpty()
->and(value: $transaction['currency'])->toBe(expected: ISOCurrencyCode::KES->name)
->and(value: $transaction['status_code'])->toBe(expected: TransactionStatusCode::INVALID->value)
->and(value: $transaction['status'])->toBe(expected: (string) TransactionStatus::INCOMPLETE->value)
->and(value: $transaction['payment_method'])->toBeEmpty()
->and(value: $transaction['description'])->toBeEmpty()
->and(value: $transaction['confirmation_code'])->toBeEmpty()
->and(value: $transaction['payment_account'])->toBeEmpty()
->and(value: $transaction['payment_status_code'])->toBeEmpty()
->and(value: $transaction['error']['error_type'])->toBe(expected: 'api_error')
->and(value: $transaction['error']['code'])->toBe(expected: 'payment_details_not_found')
->and(value: $transaction['error']['message'])->toBe(expected: 'Pending Payment');

});

0 comments on commit 487aae1

Please sign in to comment.