├── .editorconfig ├── .env.sample ├── .github ├── CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── SAMPLES.md ├── TODO.md ├── composer.json └── src ├── Paystack.php ├── Paystack ├── Contracts │ └── RouteInterface.php ├── Event.php ├── Exception │ ├── ApiException.php │ ├── BadMetaNameException.php │ ├── PaystackException.php │ └── ValidationException.php ├── Fee.php ├── Helpers │ ├── Caller.php │ └── Router.php ├── Http │ ├── Request.php │ ├── RequestBuilder.php │ └── Response.php ├── MetadataBuilder.php └── Routes │ ├── Balance.php │ ├── Bank.php │ ├── Customer.php │ ├── Decision.php │ ├── Integration.php │ ├── Invoice.php │ ├── Page.php │ ├── Plan.php │ ├── Settlement.php │ ├── Subaccount.php │ ├── Subscription.php │ ├── Transaction.php │ ├── Transfer.php │ └── Transferrecipient.php └── autoload.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PAYSTACK_SECRET_KEY="your secret key goes here" 2 | PAYSTACK_SECRET_KEY_FAKE="a fake secret key goes here" -------------------------------------------------------------------------------- /.github/CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/yabacon/paystack-php). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | **Happy coding**! 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Problem/Motivation 2 | (Why the issue was filed, steps to reproduce the problem, etc.) 3 | 4 | ## Proposed resolution 5 | (Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.) 6 | 7 | ## Repeatable 8 | Always|Sometimes|Specific conditions|Specific times 9 | 10 | (If it is a bug, you are reporting lease specify:) 11 | 12 | ## Steps to repeat: (Describe how the issue can be repeated by someone who is to work on it) 13 | 1. Step 1 14 | 2. Step 2 15 | 3. ... 16 | 17 | ## Expected Results: 18 | (What you expected steps 1, 2 and 3 to give) 19 | 20 | ## Actual Results: 21 | (What is gave including any error messages, memory dump etc that can help) 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #0 (Enter the number for the issue this fixes. If you have not yet created an issue, please do so now or delete this line if you are only submitting a patch) 2 | 3 | ## Changes made by this pull request 4 | - 5 | - 6 | - 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `paystack-php` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | ## 2.2.0 - 2020-05-04 8 | 9 | ### Added 10 | - Ability to load custom routes 11 | 12 | ## 2.1.22 - 2018-05-15 13 | 14 | ### Added 15 | - Invoice route 16 | 17 | ## 2.1.3 - 2017-01-30 18 | 19 | ### Changes 20 | - Spread logic into several classes for improved Unit Testing 21 | 22 | ### Added 23 | - Event 24 | - Fees 25 | - Subaccount Route 26 | 27 | ## 2.0.3 - 2016-12-11 28 | 29 | ### Changes 30 | - To do a direct register_autoload, include autoload.php 31 | 32 | ### Deprecated 33 | - ~Paystack::registerAutoloader();~ 34 | 35 | ## 2.0 - 2016-04-26 36 | 37 | ### Changes 38 | - Calls will return an Object of stdClass or throw a Paystack API/cURL error instead of 39 | an array as in version 1 40 | - Root namespace is now Yabacon instead of YabaCon 41 | 42 | ### Added 43 | - Pages 44 | - Subscriptions 45 | - Use ->fetch to get a single item or call singular form with id/code 46 | - Use ->list to get a list of items or call plural form with paging parameters 47 | 48 | ## 2.0.3 - 2016-12-11 49 | 50 | ### Changes 51 | - Spread logic into several classes for improved Unit Testing 52 | 53 | ### Added 54 | - Event 55 | - Fees 56 | 57 | ### Added usage of TLSv1.2 58 | CURL default SSL version TLSv1.2 59 | Update Requirements for Curl, OpenSSL and PHP 60 | define `CURL_SSLVERSION_TLSv1_2` as 6 if not found, to avoid not defined error 61 | 62 | ## 1.0.2 - 2016-03-10 63 | 64 | ### Added usage of TLSv1.2 65 | CURL default SSL version TLSv1.2 66 | Update Requirements for Curl, OpenSSL and PHP 67 | define `CURL_SSLVERSION_TLSv1_2` as 6 if not found, to avoid not defined error 68 | 69 | ### Deprecated 70 | - Nothing 71 | 72 | ### Fixed 73 | - Nothing 74 | 75 | ### Removed 76 | - Nothing 77 | 78 | ### Security 79 | - Nothing 80 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yabacon Valley 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paystack-php 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Build Status][ico-travis]][link-travis] 6 | [![Coverage Status][ico-scrutinizer]][link-scrutinizer] 7 | [![Quality Score][ico-code-quality]][link-code-quality] 8 | [![Total Downloads][ico-downloads]][link-downloads] 9 | 10 | A PHP API wrapper for [Paystack](https://paystack.co/). 11 | 12 | [![Paystack](img/paystack.png?raw=true "Paystack")](https://paystack.co/) 13 | 14 | ## Requirements 15 | - Curl 7.34.0 or more recent (Unless using Guzzle) 16 | - PHP 5.4.0 or more recent 17 | - OpenSSL v1.0.1 or more recent 18 | 19 | ## Install 20 | 21 | ### Via Composer 22 | 23 | ``` bash 24 | $ composer require yabacon/paystack-php 25 | ``` 26 | 27 | ### Via download 28 | 29 | Download a release version from the [releases page](https://github.com/yabacon/paystack-php/releases). 30 | Extract, then: 31 | ``` php 32 | require 'path/to/src/autoload.php'; 33 | ``` 34 | 35 | ## IMPORTANT 36 | Version 2 is not compatible with version 1 code! It throws an error if there's problem error in cURL 37 | or if the Paystack API gives a false status in the response body. 38 | 39 | ## Usage 40 | 41 | Do a redirect to the authorization URL received from calling the transaction/initialize endpoint. This URL is valid for one time use, so ensure that you generate a new URL per transaction. 42 | 43 | When the payment is successful, we will call your callback URL (as setup in your dashboard or while initializing the transaction) and return the reference sent in the first step as a query parameter. 44 | 45 | If you use a test secret key, we will call your test callback url, otherwise, we'll call your live callback url. 46 | 47 | ### 0. Prerequisites 48 | Confirm that your server can conclude a TLSv1.2 connection to Paystack's servers. Most up-to-date software have this capability. Contact your service provider for guidance if you have any SSL errors. 49 | *Don't disable SSL peer verification!* 50 | 51 | ### 1. Prepare your parameters 52 | `email` and `amount` are the most common compulsory parameters. Do send a unique email per customer. If your customers do not provide a unique email, please devise a strategy to set one for each of them. Any of those below work fine. The amount we accept on all endpoint are in kobo and must be an integer value. For instance, to accept `456 naira, 78 kobo`, please send `45678` as the amount. 53 | 54 | ### 2. Initialize a transaction 55 | Initialize a transaction by calling our API. 56 | 57 | ```php 58 | $paystack = new Yabacon\Paystack(SECRET_KEY); 59 | try 60 | { 61 | $tranx = $paystack->transaction->initialize([ 62 | 'amount'=>$amount, // in kobo 63 | 'email'=>$email, // unique to customers 64 | 'reference'=>$reference, // unique to transactions 65 | ]); 66 | } catch(\Yabacon\Paystack\Exception\ApiException $e){ 67 | print_r($e->getResponseObject()); 68 | die($e->getMessage()); 69 | } 70 | 71 | // store transaction reference so we can query in case user never comes back 72 | // perhaps due to network issue 73 | save_last_transaction_reference($tranx->data->reference); 74 | 75 | // redirect to page so User can pay 76 | header('Location: ' . $tranx->data->authorization_url); 77 | ``` 78 | 79 | When the user enters their card details, Paystack will validate and charge the card. It will do all the below: 80 | 81 | Redirect back to a callback_url set when initializing the transaction or on your dashboard at: https://dashboard.paystack.co/#/settings/developer . If neither is set, Customers see a Transaction was successful message. 82 | 83 | Send a charge.success event to your Webhook URL set at: https://dashboard.paystack.co/#/settings/developer 84 | 85 | If receipts are not turned off, an HTML receipt will be sent to the customer's email. 86 | 87 | Before you give value to the customer, please make a server-side call to our verification endpoint to confirm the status and properties of the transaction. 88 | 89 | ### 3. Handle charge.success Event 90 | We will post a charge.success event to the webhook URL set for your transaction's domain. If it was a live transaction, we will post to your live webhook url and vice-versa. 91 | 92 | - if using .htaccess, remember to add the trailing / to the url you set. 93 | - Do a test post to your URL and ensure the script gets the post body. 94 | - Publicly available url (http://localhost cannot receive!) 95 | 96 | ```php 97 | // Retrieve the request's body and parse it as JSON 98 | $event = Yabacon\Paystack\Event::capture(); 99 | http_response_code(200); 100 | 101 | /* It is a important to log all events received. Add code * 102 | * here to log the signature and body to db or file */ 103 | openlog('MyPaystackEvents', LOG_CONS | LOG_NDELAY | LOG_PID, LOG_USER | LOG_PERROR); 104 | syslog(LOG_INFO, $event->raw); 105 | closelog(); 106 | 107 | /* Verify that the signature matches one of your keys*/ 108 | $my_keys = [ 109 | 'live'=>'sk_live_blah', 110 | 'test'=>'sk_test_blah', 111 | ]; 112 | $owner = $event->discoverOwner($my_keys); 113 | if(!$owner){ 114 | // None of the keys matched the event's signature 115 | die(); 116 | } 117 | 118 | // Do something with $event->obj 119 | // Give value to your customer but don't give any output 120 | // Remember that this is a call from Paystack's servers and 121 | // Your customer is not seeing the response here at all 122 | switch($event->obj->event){ 123 | // charge.success 124 | case 'charge.success': 125 | if('success' === $event->obj->data->status){ 126 | // TIP: you may still verify the transaction 127 | // via an API call before giving value. 128 | } 129 | break; 130 | } 131 | ``` 132 | 133 | ### 4. Verify Transaction 134 | After we redirect to your callback url, please verify the transaction before giving value. 135 | 136 | ```php 137 | $reference = isset($_GET['reference']) ? $_GET['reference'] : ''; 138 | if(!$reference){ 139 | die('No reference supplied'); 140 | } 141 | 142 | // initiate the Library's Paystack Object 143 | $paystack = new Yabacon\Paystack(SECRET_KEY); 144 | try 145 | { 146 | // verify using the library 147 | $tranx = $paystack->transaction->verify([ 148 | 'reference'=>$reference, // unique to transactions 149 | ]); 150 | } catch(\Yabacon\Paystack\Exception\ApiException $e){ 151 | print_r($e->getResponseObject()); 152 | die($e->getMessage()); 153 | } 154 | 155 | if ('success' === $tranx->data->status) { 156 | // transaction was successful... 157 | // please check other things like whether you already gave value for this ref 158 | // if the email matches the customer who owns the product etc 159 | // Give value 160 | } 161 | ``` 162 | 163 | ## 5. Closing Notes 164 | 165 | Generally, to make an API request after constructing a paystack object, Make a call 166 | to the resource/method thus: `$paystack->{resource}->{method}()`; for gets, 167 | use `$paystack->{resource}(id)` and to list resources: `$paystack->{resource}s()`. 168 | 169 | Currently, we support: 'customer', 'page', 'plan', 'subscription', 'transaction' and 'subaccount'. Check 170 | our API reference([link-paystack-api-reference][link-paystack-api-reference]) for the methods supported. To specify parameters, send as an array. 171 | 172 | Check [SAMPLES](SAMPLES.md) for more sample calls 173 | 174 | ## Extras 175 | 176 | There are classes that should help with some tasks developers need to do on Paystack often: 177 | 178 | ### Fee 179 | 180 | This class works with an amount and your Paystack fees. To use, create a new Fee object 181 | 182 | ```php 183 | $fee = new Yabacon\Paystack\Fee(); 184 | ``` 185 | Configure it: 186 | 187 | ```php 188 | $fee->withPercentage(0.015); // 1.5% 189 | $fee->withAdditionalCharge(10000); // plus 100 NGN 190 | $fee->withThreshold(250000); // when total is above 2,500 NGN 191 | $fee->withCap(200000); // capped at 2000 192 | ``` 193 | 194 | Calculate fees 195 | ```php 196 | $chargeOn300naira = $fee->calculateFor(30000); 197 | $chargeOn7000naira = $fee->calculateFor(700000); 198 | ``` 199 | 200 | To know what to send to the API when you want to be settled a particular amount 201 | ```php 202 | $iWant100Naira = $fee->addFor(10000); 203 | $iWant4000Naira = $fee->addFor(400000); 204 | ``` 205 | 206 | ### Event 207 | 208 | This class helps you capture and our events: 209 | ```php 210 | $event = Yabacon\Paystack\Event::capture(); 211 | ``` 212 | 213 | Verify it against a key: 214 | ```php 215 | if($event->validFor($mySecretKey)) 216 | { 217 | // awesome 218 | } 219 | ``` 220 | 221 | Discover the key that owns it (IN case same webhook receives for several integrations). This can 222 | come in handy for multi-tenant systems. Or simply if you want to have same test and live webhook url. 223 | ```php 224 | $my_keys = [ 225 | 'live'=>'sk_live_blah', 226 | 'test'=>'sk_test_blah', 227 | ]; 228 | $owner = $event->discoverOwner($my_keys); 229 | if(!$owner){ 230 | // None of my keys matched the event's signature 231 | die(); 232 | } 233 | ``` 234 | 235 | To forward the event to another url. In case you want other systems to be notified of exact same event. 236 | ```php 237 | $evt->forwardTo('http://another-webhook.url'); 238 | ``` 239 | 240 | ### MetadataBuilder 241 | 242 | This class helps you build valid json metadata strings to be sent when making transaction requests. 243 | ```php 244 | $builder = new MetadataBuilder(); 245 | ``` 246 | 247 | #### Add metadata 248 | 249 | Add MetaData by calling the `withKeyName` magic function (These will not be shown on dashboard). 250 | Do not use `CustomFields` as a KeyName 251 | 252 | ```php 253 | $builder->withQuoteId(10); // will add as quote_id: 10 unless you turn auto_snake_case off 254 | $builder->withMobileNumber(08012345678); // will add as mobile_number: 08012345678 255 | $builder->withCSRF('dontacceptpaymentunlessthismatches'); 256 | ``` 257 | 258 | To turn off automatic snake-casing of Key names, do: 259 | 260 | ```php 261 | MetadataBuilder::$auto_snake_case = false; 262 | ``` 263 | 264 | before you start adding metadata to the `$builder`. 265 | 266 | #### Add Custom Fields 267 | 268 | Add Custom Fields by calling the `withCustomField` function (These will shown on dashboard). 269 | 270 | ```php 271 | $builder->withCustomField('Mobile Number', '080123456789'); 272 | $builder->withCustomField('See Me', 'I\'m Visible on your Dashboard'); 273 | ``` 274 | 275 | #### Build JSON 276 | 277 | Finally call `build()` to get your JSON metadata string. 278 | 279 | ### Using Custom Routes 280 | 281 | You can add your custom routes by calling the `useRoutes` method on the paystack object. 282 | 283 | ```php 284 | $paystack = new Yabacon\Paystack(SECRET_KEY); 285 | $paystack->useRoutes(["charge" => Charge::class]); 286 | 287 | $paystack->charge->chargeMobileMoney([ 288 | 'email' => 'hey@example.com', 289 | 'reference' => 'trnx_ref', 290 | 'amount' => 50 * 100, 291 | 'currency' => 'GHS', 292 | 'mobile_money' => [ 293 | 'phone' => '5533467', 294 | 'provider' => 'MTN' 295 | ] 296 | ]); 297 | ``` 298 | Your custom routes should implement the ```Yabacon\Paystack\Contracts\RouteInterface``` contract 299 | 300 | ```php 301 | class Charge implements RouteInterface 302 | { 303 | 304 | public static function root() 305 | { 306 | return '/charge'; 307 | } 308 | 309 | public static function chargeMobileMoney() 310 | { 311 | return [ 312 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 313 | RouteInterface::ENDPOINT_KEY => Charge::root(), 314 | RouteInterface::PARAMS_KEY => [ 315 | 'email', 316 | 'reference', 317 | 'amount', 318 | 'currency', 319 | 'mobile_money', 320 | ], 321 | ]; 322 | } 323 | } 324 | ``` 325 | 326 | ## Change log 327 | 328 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 329 | 330 | ## Testing 331 | 332 | ``` bash 333 | $ composer test 334 | ``` 335 | 336 | ## Contributing 337 | 338 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CONDUCT](.github/CONDUCT.md) for details. Check our [todo list](TODO.md) for features already intended. 339 | 340 | ## Security 341 | 342 | If you discover any security related issues, please email yabacon.valley@gmail.com instead of using the issue tracker. 343 | 344 | ## Credits 345 | 346 | - [Yabacon][link-author] 347 | - [Issa Jubril](https://github.com/masterp4dev) 348 | - [Akachukwu Okafor](https://github.com/kasoprecede47) 349 | - [Ibrahim Lawal](https://github.com/ibrahimlawal) 350 | - [Opeyemi Obembe](https://github.com/kehers) - followed the style he employed in creating the [NodeJS Wrapper](https://github.com/kehers/paystack) 351 | - [All Contributors][link-contributors] 352 | 353 | ## License 354 | 355 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 356 | 357 | [ico-version]: https://img.shields.io/packagist/v/yabacon/paystack-php.svg?style=flat-square 358 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 359 | [ico-travis]: https://img.shields.io/travis/yabacon/paystack-php/master.svg?style=flat-square 360 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/yabacon/paystack-php.svg?style=flat-square 361 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/yabacon/paystack-php.svg?style=flat-square 362 | [ico-downloads]: https://img.shields.io/packagist/dt/yabacon/paystack-php.svg?style=flat-square 363 | 364 | [link-packagist]: https://packagist.org/packages/yabacon/paystack-php 365 | [link-travis]: https://travis-ci.org/yabacon/paystack-php 366 | [link-scrutinizer]: https://scrutinizer-ci.com/g/yabacon/paystack-php/code-structure 367 | [link-code-quality]: https://scrutinizer-ci.com/g/yabacon/paystack-php 368 | [link-downloads]: https://packagist.org/packages/yabacon/paystack-php 369 | [link-author]: https://github.com/yabacon 370 | [link-contributors]: ../../contributors 371 | [link-paystack-api-reference]: https://developers.paystack.co/reference 372 | -------------------------------------------------------------------------------- /SAMPLES.md: -------------------------------------------------------------------------------- 1 | #Sample calls 2 | 3 | Assumes that you already installed and configured Yabacon\Paystack. And that you have created and 4 | configured the $paystack object as you want. Check [README](README.md) for details. 5 | 6 | ``` php 7 | // Make a call to the resource/method 8 | // $paystack->{resource}->{method}(); 9 | // for gets, use $paystack->{resource}(id) 10 | // for lists, use $paystack->{resource}s() 11 | 12 | // invoice 13 | $paystack->invoice(12); 14 | $paystack->invoice->list(); 15 | $paystack->invoice->fetch("FShsjksl"); 16 | $paystack->invoices(); 17 | $paystack->invoice->create([ 18 | 'customer' => 'FShsjksl', 19 | 'amount' => 5000, 20 | 'due_date' => '2018-08-26' 21 | ]); 22 | 23 | // customer 24 | $paystack->customer(12); 25 | $paystack->customer->list(); 26 | $paystack->customer->fetch(12); 27 | $paystack->customers(); 28 | $paystack->customer->create([ 29 | 'first_name'=>'name', 30 | 'last_name'=>'name', 31 | 'email'=>'email', 32 | 'phone'=>'phone' 33 | ]); 34 | $paystack->customer->update([ 35 | 'id'=>233, 36 | 'first_name'=>'name', 37 | 'last_name'=>'name', 38 | 'email'=>'email', 39 | 'phone'=>'phone' 40 | ]); 41 | $paystack->customer->list(['perPage'=>5,'page'=>2]); // list the second page at 5 customers per page 42 | 43 | // plan 44 | $paystack->plan(12); 45 | $paystack->plans(); 46 | $paystack->plan->fetch("PLNxxx"); 47 | $paystack->plan->list(); 48 | $paystack->plan->create([ 49 | 'name'=>'name', 50 | 'description'=>'Describe at length', 51 | 'amount'=>1000, // in kobo 52 | 'interval'=>7, 53 | 'send_invoices'=>true, 54 | 'send_sms'=>true, 55 | 'hosted_page'=>'url', 56 | 'hosted_page_url'=>'url', 57 | 'hosted_page_summary'=>'details', 58 | 'currency'=>'NGN' 59 | ]); 60 | $paystack->plan->update([ 61 | 'name'=>'name', 62 | 'description'=>'Describe at length', 63 | 'amount'=>1000, // in kobo 64 | 'interval'=>'weekly', 65 | 'send_invoices'=>true, 66 | 'send_sms'=>true, 67 | 'hosted_page'=>'url', 68 | 'hosted_page_url'=>'url', 69 | 'hosted_page_summary'=>'details', 70 | 'currency'=>'NGN' 71 | ],['id'=>233]); 72 | $paystack->plan->list(['perPage'=>5,'page'=>2]); // list the second page at 5 per page 73 | 74 | // page 75 | $paystack->page(12); 76 | $paystack->pages(); 77 | $paystack->page->fetch(12); 78 | $paystack->page->list(); 79 | $paystack->page->create([ 80 | 'name'=>'name', 81 | 'description'=>'Describe at length', 82 | 'amount'=>1000 83 | ]); 84 | $paystack->page->update([ 85 | 'name'=>'name', 86 | 'description'=>'Describe at length'],['id'=>233]); 87 | $paystack->page->list(['perPage'=>5,'page'=>2]); // list the second page at 5 per page 88 | 89 | // page 90 | $paystack->subscription(12); 91 | $paystack->subscription(); 92 | $paystack->subscription->fetch(12); 93 | $paystack->subscription->list(); 94 | $paystack->subscription->create([ 95 | 'plan'=>'PLN_xxxx', 96 | 'customer'=>'CUS_xxxxx', 97 | 'authorization'=>'AUTH_xxx' 98 | ]); 99 | $paystack->subscription->disable([ 100 | 'code'=>'', 101 | 'token'=>''],['id'=>233]); 102 | $paystack->subscription->list(['perPage'=>5,'page'=>2]); // list the second page at 5 per page 103 | $paystack->subscriptions(['perPage'=>5,'page'=>2]); // list the second page at 5 per page 104 | 105 | // transaction 106 | $paystack->transaction(12); 107 | $paystack->transaction->list(); 108 | $paystack->transaction->initialize([ 109 | 'reference'=>'unique', 110 | 'amount'=>19000, // in kobo 111 | 'email'=>'e@ma.il', 112 | 'plan'=>1 // optional, don't include unless it has a value 113 | ]); 114 | $paystack->transaction->charge([ 115 | 'reference'=>'unique', 116 | 'authorization_code'=>'auth_code', 117 | 'email'=>'e@ma.il', 118 | 'amount'=>1000 // in kobo 119 | ]); 120 | $paystack->transaction->chargeToken([ 121 | 'reference'=>'unique', 122 | 'token'=>'pstk_token', 123 | 'email'=>'e@ma.il', 124 | 'amount'=>1000 // in kobo 125 | ]); 126 | $paystack->transaction->list(['perPage'=>5,'page'=>2]); // list the second page at 5 transactions per page 127 | 128 | $paystack->transaction->verify([ 129 | 'reference'=>'unique_refencecode' 130 | ]); 131 | $paystack->transaction->totals(); 132 | 133 | 134 | ``` 135 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODOs 2 | 3 | - Proper resource examples - [SAMPLES.md](SAMPLES.md) 4 | - Tests 5 | - Paystack Exception Classes and throwing 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yabacon/paystack-php", 3 | "type": "library", 4 | "description": "Helps make your Paystack API calls giving a stdClass object.", 5 | "keywords": [ 6 | "yabacon", 7 | "paystack-php", 8 | "paystack", 9 | "plan", 10 | "page", 11 | "api", 12 | "transaction", 13 | "customer", 14 | "subscription", 15 | "enable", 16 | "disable", 17 | "verify", 18 | "curl" 19 | ], 20 | "homepage": "https://github.com/yabacon/paystack-php", 21 | "license": "MIT", 22 | "authors": [ 23 | { 24 | "name": "Yabacon", 25 | "email": "yabacon.valley@gmail.com", 26 | "homepage": "https://github.com/yabacon", 27 | "role": "Owner" 28 | }, 29 | { 30 | "name": "Ibrahim Lawal", 31 | "email": "ibrahim@lawal.me", 32 | "homepage": "http://ibrahim.lawal.me", 33 | "role": "Developer" 34 | } 35 | ], 36 | "require": { 37 | }, 38 | "require-dev": { 39 | "phpunit/phpunit": "~5.3", 40 | "scrutinizer/ocular": "^1.1", 41 | "guzzlehttp/guzzle": "^6.2", 42 | "squizlabs/php_codesniffer": "^2.3", 43 | "vlucas/phpdotenv": "^2.2" 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "Yabacon\\": "src/" 48 | } 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Yabacon\\Paystack\\Test\\": "tests/" 53 | } 54 | }, 55 | "scripts": { 56 | "test": "phpunit" 57 | }, 58 | "extra": { 59 | "branch-alias": { 60 | "dev-master": "2.0-dev" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Paystack.php: -------------------------------------------------------------------------------- 1 | secret_key = $secret_key; 23 | } 24 | 25 | public function useGuzzle() 26 | { 27 | $this->use_guzzle = true; 28 | } 29 | 30 | public function useRoutes(array $routes) 31 | { 32 | foreach ($routes as $route => $class) { 33 | if (! is_string($route)) { 34 | throw new \InvalidArgumentException( 35 | 'Custom routes should map to a route class' 36 | ); 37 | } 38 | 39 | if (in_array($route, Router::$ROUTES)) { 40 | throw new \InvalidArgumentException( 41 | $route . ' is already an existing defined route' 42 | ); 43 | } 44 | 45 | if (! in_array(RouteInterface::class, class_implements($class))) { 46 | throw new \InvalidArgumentException( 47 | 'Custom route class ' . $class . 'should implement ' . RouteInterface::class 48 | ); 49 | } 50 | } 51 | 52 | $this->custom_routes = $routes; 53 | } 54 | 55 | public static function disableFileGetContentsFallback() 56 | { 57 | Paystack::$fallback_to_file_get_contents = false; 58 | } 59 | 60 | public static function enableFileGetContentsFallback() 61 | { 62 | Paystack::$fallback_to_file_get_contents = true; 63 | } 64 | 65 | public function __call($method, $args) 66 | { 67 | if ($singular_form = Router::singularFor($method)) { 68 | return $this->handlePlural($singular_form, $method, $args); 69 | } 70 | return $this->handleSingular($method, $args); 71 | } 72 | 73 | private function handlePlural($singular_form, $method, $args) 74 | { 75 | if ((count($args) === 1 && is_array($args[0]))||(count($args) === 0)) { 76 | return $this->{$singular_form}->__call('getList', $args); 77 | } 78 | throw new \InvalidArgumentException( 79 | 'Route "' . $method . '" can only accept an optional array of filters and ' 80 | .'paging arguments (perPage, page).' 81 | ); 82 | } 83 | 84 | private function handleSingular($method, $args) 85 | { 86 | if (count($args) === 1) { 87 | $args = [[], [ Router::ID_KEY => $args[0] ] ]; 88 | return $this->{$method}->__call('fetch', $args); 89 | } 90 | throw new \InvalidArgumentException( 91 | 'Route "' . $method . '" can only accept an id or code.' 92 | ); 93 | } 94 | 95 | /** 96 | * @deprecated 97 | */ 98 | public static function registerAutoloader() 99 | { 100 | trigger_error('Include "src/autoload.php" instead', E_DEPRECATED | E_USER_NOTICE); 101 | require_once(__DIR__ . '/../src/autoload.php'); 102 | } 103 | 104 | public function __get($name) 105 | { 106 | return new Router($name, $this); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Paystack/Contracts/RouteInterface.php: -------------------------------------------------------------------------------- 1 | raw = @file_get_contents('php://input'); 21 | $evt->signature = ( isset($_SERVER[self::SIGNATURE_KEY]) ? $_SERVER[self::SIGNATURE_KEY] : '' ); 22 | $evt->loadObject(); 23 | return $evt; 24 | } 25 | 26 | protected function loadObject() 27 | { 28 | $this->obj = json_decode($this->raw); 29 | } 30 | 31 | public function discoverOwner(array $keys) 32 | { 33 | if (!$this->obj || !property_exists($this->obj, 'data')) { 34 | return; 35 | } 36 | foreach ($keys as $index => $key) { 37 | if ($this->validFor($key)) { 38 | return $index; 39 | } 40 | } 41 | } 42 | 43 | public function validFor($key) 44 | { 45 | if ($this->signature === hash_hmac('sha512', $this->raw, $key)) { 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | public function package(array $additional_headers = [], $method = 'POST') 52 | { 53 | $pack = new Request(); 54 | $pack->method = $method; 55 | $pack->headers = $additional_headers; 56 | $pack->headers["X-Paystack-Signature"] = $this->signature; 57 | $pack->headers["Content-Type"] = "application/json"; 58 | $pack->body = $this->raw; 59 | return $pack; 60 | } 61 | 62 | public function forwardTo($url, array $additional_headers = [], $method = 'POST') 63 | { 64 | if (!filter_var($url, FILTER_VALIDATE_URL)) { 65 | return false; 66 | } 67 | $packed = $this->package($additional_headers, $method); 68 | $packed->endpoint = $url; 69 | return $packed->send()->wrapUp(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Paystack/Exception/ApiException.php: -------------------------------------------------------------------------------- 1 | responseObject = $responseObject; 14 | $this->requestObject = $requestObject; 15 | } 16 | 17 | public function getResponseObject() 18 | { 19 | return $this->responseObject; 20 | } 21 | 22 | public function getRequestObject() 23 | { 24 | return $this->requestObject; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Paystack/Exception/BadMetaNameException.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Paystack/Exception/PaystackException.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Paystack/Fee.php: -------------------------------------------------------------------------------- 1 | percentage = Fee::$default_percentage; 29 | $this->additional_charge = Fee::$default_additional_charge; 30 | $this->threshold = Fee::$default_threshold; 31 | $this->cap = Fee::$default_cap; 32 | $this->__setup(); 33 | } 34 | 35 | public function withPercentage($percentage) 36 | { 37 | $this->percentage = $percentage; 38 | $this->__setup(); 39 | } 40 | 41 | public static function resetDefaults() 42 | { 43 | Fee::$default_percentage = Fee::DEFAULT_PERCENTAGE; 44 | Fee::$default_additional_charge = Fee::DEFAULT_ADDITIONAL_CHARGE; 45 | Fee::$default_threshold = Fee::DEFAULT_THRESHOLD; 46 | Fee::$default_cap = Fee::DEFAULT_CAP; 47 | } 48 | 49 | public function withAdditionalCharge($additional_charge) 50 | { 51 | $this->additional_charge = $additional_charge; 52 | $this->__setup(); 53 | } 54 | 55 | public function withThreshold($threshold) 56 | { 57 | $this->threshold = $threshold; 58 | $this->__setup(); 59 | } 60 | 61 | public function withCap($cap) 62 | { 63 | $this->cap = $cap; 64 | $this->__setup(); 65 | } 66 | 67 | private function __setup() 68 | { 69 | $this->chargeDivider = $this->__chargeDivider(); 70 | $this->crossover = $this->__crossover(); 71 | $this->flatlinePlusCharge = $this->__flatlinePlusCharge(); 72 | $this->flatline = $this->__flatline(); 73 | } 74 | 75 | private function __chargeDivider() 76 | { 77 | return 1 - $this->percentage; 78 | } 79 | 80 | private function __crossover() 81 | { 82 | return ($this->threshold * $this->chargeDivider) - $this->additional_charge; 83 | } 84 | 85 | private function __flatlinePlusCharge() 86 | { 87 | return ($this->cap - $this->additional_charge) / $this->percentage; 88 | } 89 | 90 | private function __flatline() 91 | { 92 | return $this->flatlinePlusCharge - $this->cap; 93 | } 94 | 95 | public function addFor($amountinkobo) 96 | { 97 | if ($amountinkobo > $this->flatline) { 98 | return intval(ceil($amountinkobo + $this->cap)); 99 | } elseif ($amountinkobo > $this->crossover) { 100 | return intval(ceil(($amountinkobo + $this->additional_charge) / $this->chargeDivider)); 101 | } else { 102 | return intval(ceil($amountinkobo / $this->chargeDivider)); 103 | } 104 | } 105 | 106 | public function calculateFor($amountinkobo) 107 | { 108 | $fee = $this->percentage * $amountinkobo; 109 | if ($amountinkobo >= $this->threshold) { 110 | $fee += $this->additional_charge; 111 | } 112 | if ($fee > $this->cap) { 113 | $fee = $this->cap; 114 | } 115 | return intval(ceil($fee)); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Paystack/Helpers/Caller.php: -------------------------------------------------------------------------------- 1 | paystackObj = $paystackObj; 15 | } 16 | 17 | public function callEndpoint($interface, $payload = [ ], $sentargs = [ ]) 18 | { 19 | $builder = new RequestBuilder($this->paystackObj, $interface, $payload, $sentargs); 20 | return $builder->build()->send()->wrapUp(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Paystack/Helpers/Router.php: -------------------------------------------------------------------------------- 1 | 'customer', 21 | 'invoices'=>'invoice', 22 | 'pages'=>'page', 23 | 'plans'=>'plan', 24 | 'subscriptions'=>'subscription', 25 | 'transactions'=>'transaction', 26 | 'banks'=>'bank', 27 | 'settlements'=>'settlement', 28 | 'transfers'=>'transfer', 29 | 'transferrecipients'=>'transferrecipient', 30 | ]; 31 | 32 | const ID_KEY = 'id'; 33 | const PAYSTACK_API_ROOT = 'https://api.paystack.co'; 34 | 35 | public function __call($methd, $sentargs) 36 | { 37 | $method = ($methd === 'list' ? 'getList' : $methd ); 38 | if (array_key_exists($method, $this->methods) && is_callable($this->methods[$method])) { 39 | return call_user_func_array($this->methods[$method], $sentargs); 40 | } else { 41 | throw new \Exception('Function "' . $method . '" does not exist for "' . $this->route . '".'); 42 | } 43 | } 44 | 45 | public static function singularFor($method) 46 | { 47 | return ( 48 | array_key_exists($method, Router::$ROUTE_SINGULAR_LOOKUP) ? 49 | Router::$ROUTE_SINGULAR_LOOKUP[$method] : 50 | null 51 | ); 52 | } 53 | 54 | public function __construct($route, $paystackObj) 55 | { 56 | $routes = $this->getAllRoutes($paystackObj); 57 | 58 | if (!in_array($route, $routes)) { 59 | throw new ValidationException( 60 | "Route '{$route}' does not exist." 61 | ); 62 | } 63 | 64 | $this->route = strtolower($route); 65 | $this->route_class = $this->getRouteClass($paystackObj); 66 | 67 | $mets = get_class_methods($this->route_class); 68 | if (empty($mets)) { 69 | throw new \InvalidArgumentException('Class "' . $this->route . '" does not exist.'); 70 | } 71 | // add methods to this object per method, except root 72 | foreach ($mets as $mtd) { 73 | if ($mtd === 'root') { 74 | continue; 75 | } 76 | $mtdFunc = function ( 77 | array $params = [ ], 78 | array $sentargs = [ ] 79 | ) use ( 80 | $mtd, 81 | $paystackObj 82 | ) { 83 | $interface = call_user_func($this->route_class . '::' . $mtd); 84 | // TODO: validate params and sentargs against definitions 85 | $caller = new Caller($paystackObj); 86 | return $caller->callEndpoint($interface, $params, $sentargs); 87 | }; 88 | $this->methods[$mtd] = \Closure::bind($mtdFunc, $this, get_class()); 89 | } 90 | } 91 | 92 | private function getAllRoutes($paystackObj) 93 | { 94 | return array_merge(static::$ROUTES, array_keys($paystackObj->custom_routes)); 95 | } 96 | 97 | private function getRouteClass($paystackObj) 98 | { 99 | if (isset($paystackObj->custom_routes[$this->route])) { 100 | return $paystackObj->custom_routes[$this->route]; 101 | } 102 | 103 | return 'Yabacon\\Paystack\\Routes\\' . ucwords($this->route); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Paystack/Http/Request.php: -------------------------------------------------------------------------------- 1 | response = new Response(); 20 | $this->response->setRequestObject($this); 21 | $this->paystackObj = $paystackObj; 22 | $this->response->forApi = !is_null($paystackObj); 23 | if ($this->response->forApi) { 24 | $this->headers['Content-Type'] = 'application/json'; 25 | } 26 | } 27 | 28 | public function getResponse() 29 | { 30 | return $this->response; 31 | } 32 | 33 | public function flattenedHeaders() 34 | { 35 | $_ = []; 36 | foreach ($this->headers as $key => $value) { 37 | $_[] = $key . ": " . $value; 38 | } 39 | return $_; 40 | } 41 | 42 | public function send() 43 | { 44 | $this->attemptGuzzle(); 45 | if (!$this->response->okay) { 46 | $this->attemptCurl(); 47 | } 48 | if (!$this->response->okay) { 49 | $this->attemptFileGetContents(); 50 | } 51 | return $this->response; 52 | } 53 | 54 | public function attemptGuzzle() 55 | { 56 | if (isset($this->paystackObj) && !$this->paystackObj->use_guzzle) { 57 | $this->response->okay = false; 58 | return; 59 | } 60 | if (class_exists('\\GuzzleHttp\\Exception\\BadResponseException') 61 | && class_exists('\\GuzzleHttp\\Exception\\ClientException') 62 | && class_exists('\\GuzzleHttp\\Exception\\ConnectException') 63 | && class_exists('\\GuzzleHttp\\Exception\\RequestException') 64 | && class_exists('\\GuzzleHttp\\Exception\\ServerException') 65 | && class_exists('\\GuzzleHttp\\Client') 66 | && class_exists('\\GuzzleHttp\\Psr7\\Request') 67 | ) { 68 | $request = new \GuzzleHttp\Psr7\Request( 69 | strtoupper($this->method), 70 | $this->endpoint, 71 | $this->headers, 72 | $this->body 73 | ); 74 | $client = new \GuzzleHttp\Client(); 75 | try { 76 | $psr7response = $client->send($request); 77 | $this->response->body = $psr7response->getBody()->getContents(); 78 | $this->response->okay = true; 79 | } catch (\Exception $e) { 80 | if (($e instanceof \GuzzleHttp\Exception\BadResponseException 81 | || $e instanceof \GuzzleHttp\Exception\ClientException 82 | || $e instanceof \GuzzleHttp\Exception\ConnectException 83 | || $e instanceof \GuzzleHttp\Exception\RequestException 84 | || $e instanceof \GuzzleHttp\Exception\ServerException) 85 | ) { 86 | if ($e->hasResponse()) { 87 | $this->response->body = $e->getResponse()->getBody()->getContents(); 88 | } 89 | $this->response->okay = true; 90 | } 91 | $this->response->messages[] = $e->getMessage(); 92 | } 93 | } 94 | } 95 | 96 | public function attemptFileGetContents() 97 | { 98 | if (!Paystack::$fallback_to_file_get_contents) { 99 | return; 100 | } 101 | $context = stream_context_create( 102 | [ 103 | 'http'=>array( 104 | 'method'=>$this->method, 105 | 'header'=>$this->flattenedHeaders(), 106 | 'content'=>$this->body, 107 | 'ignore_errors' => true 108 | ) 109 | ] 110 | ); 111 | $this->response->body = file_get_contents($this->endpoint, false, $context); 112 | if ($this->response->body === false) { 113 | $this->response->messages[] = 'file_get_contents failed with response: \'' . error_get_last() . '\'.'; 114 | } else { 115 | $this->response->okay = true; 116 | } 117 | } 118 | 119 | public function attemptCurl() 120 | { 121 | //open connection 122 | $ch = \curl_init(); 123 | \curl_setopt($ch, \CURLOPT_URL, $this->endpoint); 124 | ($this->method === RouteInterface::POST_METHOD) && \curl_setopt($ch, \CURLOPT_POST, true); 125 | ($this->method === RouteInterface::PUT_METHOD) && \curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'PUT'); 126 | 127 | if ($this->method !== RouteInterface::GET_METHOD) { 128 | \curl_setopt($ch, \CURLOPT_POSTFIELDS, $this->body); 129 | } 130 | \curl_setopt($ch, \CURLOPT_HTTPHEADER, $this->flattenedHeaders()); 131 | \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1); 132 | $this->response->forApi && \curl_setopt($ch, \CURLOPT_SSLVERSION, 6); 133 | 134 | $this->response->body = \curl_exec($ch); 135 | 136 | if (\curl_errno($ch)) { 137 | $cerr = \curl_error($ch); 138 | $this->response->messages[] = 'Curl failed with response: \'' . $cerr . '\'.'; 139 | } else { 140 | $this->response->okay = true; 141 | } 142 | 143 | \curl_close($ch); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Paystack/Http/RequestBuilder.php: -------------------------------------------------------------------------------- 1 | request = new Request($paystackObj); 21 | $this->paystackObj = $paystackObj; 22 | $this->interface = $interface; 23 | $this->payload = $payload; 24 | $this->sentargs = $sentargs; 25 | } 26 | 27 | public function build() 28 | { 29 | $this->request->headers["Authorization"] = "Bearer " . $this->paystackObj->secret_key; 30 | $this->request->headers["User-Agent"] = "Paystack/v1 PhpBindings/" . Paystack::VERSION; 31 | $this->request->endpoint = Router::PAYSTACK_API_ROOT . $this->interface[RouteInterface::ENDPOINT_KEY]; 32 | $this->request->method = $this->interface[RouteInterface::METHOD_KEY]; 33 | $this->moveArgsToSentargs(); 34 | $this->putArgsIntoEndpoint($this->request->endpoint); 35 | $this->packagePayload(); 36 | return $this->request; 37 | } 38 | 39 | public function packagePayload() 40 | { 41 | if (is_array($this->payload) && count($this->payload)) { 42 | if ($this->request->method === RouteInterface::GET_METHOD) { 43 | $this->request->endpoint = $this->request->endpoint . '?' . http_build_query($this->payload); 44 | } else { 45 | $this->request->body = json_encode($this->payload); 46 | } 47 | } 48 | } 49 | 50 | public function putArgsIntoEndpoint(&$endpoint) 51 | { 52 | foreach ($this->sentargs as $key => $value) { 53 | $endpoint = str_replace('{' . $key . '}', $value, $endpoint); 54 | } 55 | } 56 | 57 | public function moveArgsToSentargs() 58 | { 59 | if (!array_key_exists(RouteInterface::ARGS_KEY, $this->interface)) { 60 | return; 61 | } 62 | $args = $this->interface[RouteInterface::ARGS_KEY]; 63 | foreach ($this->payload as $key => $value) { 64 | if (in_array($key, $args)) { 65 | $this->sentargs[$key] = $value; 66 | unset($this->payload[$key]); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Paystack/Http/Response.php: -------------------------------------------------------------------------------- 1 | requestObject = $requestObject; 19 | } 20 | 21 | public function getRequestObject() 22 | { 23 | return $this->requestObject; 24 | } 25 | 26 | private function parsePaystackResponse() 27 | { 28 | $resp = \json_decode($this->body); 29 | 30 | if ($resp === null || !property_exists($resp, 'status') || !$resp->status) { 31 | throw new ApiException( 32 | "Paystack Request failed with response: '" . 33 | $this->messageFromApiJson($resp)."'", 34 | $resp, 35 | $this->requestObject 36 | ); 37 | } 38 | 39 | return $resp; 40 | } 41 | 42 | private function messageFromApiJson($resp) 43 | { 44 | $message = $this->body; 45 | if ($resp !== null) { 46 | if (property_exists($resp, 'message')) { 47 | $message = $resp->message; 48 | } 49 | if (property_exists($resp, 'errors') && ($resp->errors instanceof \stdClass)) { 50 | $message .= "\nErrors:\n"; 51 | foreach ($resp->errors as $field => $errors) { 52 | $message .= "\t" . $field . ":\n"; 53 | foreach ($errors as $_unused => $error) { 54 | $message .= "\t\t" . $error->rule . ": "; 55 | $message .= $error->message . "\n"; 56 | } 57 | } 58 | } 59 | } 60 | return $message; 61 | } 62 | 63 | private function implodedMessages() 64 | { 65 | return implode("\n\n", $this->messages); 66 | } 67 | 68 | public function wrapUp() 69 | { 70 | if ($this->okay && $this->forApi) { 71 | return $this->parsePaystackResponse(); 72 | } 73 | if (!$this->okay && $this->forApi) { 74 | throw new \Exception($this->implodedMessages()); 75 | } 76 | if ($this->okay) { 77 | return $this->body; 78 | } 79 | error_log($this->implodedMessages()); 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Paystack/MetadataBuilder.php: -------------------------------------------------------------------------------- 1 | meta = []; 14 | } 15 | 16 | private function with($name, $value) 17 | { 18 | if ($name === 'custom_fields') { 19 | throw new BadMetaNameException('Please use the withCustomField method to add custom fields'); 20 | } 21 | $this->meta[$name] = $value; 22 | return $this; 23 | } 24 | 25 | private function toSnakeCase($input) 26 | { 27 | preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); 28 | $ret = $matches[0]; 29 | foreach ($ret as &$match) { 30 | $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match); 31 | } 32 | return implode('_', $ret); 33 | } 34 | 35 | public function __call($method, $args) 36 | { 37 | if ((strpos($method, 'with') === 0) && ($method !== 'with')) { 38 | $name = substr($method, 4); 39 | if (MetadataBuilder::$auto_snake_case) { 40 | $name = $this->toSnakeCase($name); 41 | } 42 | return $this->with($name, $args[0]); 43 | } 44 | throw new \BadMethodCallException('Call to undefined function: ' . get_class($this) . '::' . $method); 45 | } 46 | 47 | public function withCustomField($title, $value) 48 | { 49 | if (!array_key_exists('custom_fields', $this->meta)) { 50 | $this->meta['custom_fields'] = []; 51 | } 52 | $this->meta['custom_fields'][] = [ 53 | 'display_name' => strval($title), 54 | 'variable_name' => strval($title), 55 | 'value' => strval($value), 56 | ]; 57 | return $this; 58 | } 59 | 60 | public function build() 61 | { 62 | return json_encode($this->meta); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Balance.php: -------------------------------------------------------------------------------- 1 | RouteInterface::GET_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Balance::root(), 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Bank.php: -------------------------------------------------------------------------------- 1 | RouteInterface::GET_METHOD, 18 | RouteInterface::ENDPOINT_KEY => Bank::root(), 19 | ]; 20 | } 21 | 22 | public static function resolveBvn() 23 | { 24 | return [ 25 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 26 | RouteInterface::ENDPOINT_KEY => Bank::root() . '/resolve_bvn/{bvn}', 27 | RouteInterface::ARGS_KEY => ['bvn'], 28 | ]; 29 | } 30 | 31 | public static function resolve() 32 | { 33 | return [ 34 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 35 | RouteInterface::ENDPOINT_KEY => Bank::root() . '/resolve', 36 | RouteInterface::PARAMS_KEY => [ 37 | 'account_number', 38 | 'bank_code', 39 | ], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Customer.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Customer::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'first_name', 22 | 'last_name', 23 | 'email', 24 | 'metadata', 25 | 'phone', 26 | ], 27 | RouteInterface::REQUIRED_KEY => [ 28 | RouteInterface::PARAMS_KEY => [ 29 | 'first_name', 30 | 'last_name', 31 | 'email', 32 | ], 33 | ], 34 | ]; 35 | } 36 | 37 | public static function fetch() 38 | { 39 | return [ 40 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 41 | RouteInterface::ENDPOINT_KEY => Customer::root() . '/{id}', 42 | RouteInterface::ARGS_KEY => ['id'], 43 | RouteInterface::REQUIRED_KEY => [RouteInterface::ARGS_KEY => ['id']], 44 | ]; 45 | } 46 | 47 | public static function getList() 48 | { 49 | return [ 50 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 51 | RouteInterface::ENDPOINT_KEY => Customer::root(), 52 | RouteInterface::PARAMS_KEY => [ 53 | 'perPage', 54 | 'page', 55 | ], 56 | ]; 57 | } 58 | 59 | public static function update() 60 | { 61 | return [ 62 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 63 | RouteInterface::ENDPOINT_KEY => Customer::root() . '/{id}', 64 | RouteInterface::PARAMS_KEY => [ 65 | 'first_name', 66 | 'last_name', 67 | 'email', 68 | 'metadata', 69 | 'phone', 70 | ], 71 | RouteInterface::ARGS_KEY => ['id'], 72 | ]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Decision.php: -------------------------------------------------------------------------------- 1 | RouteInterface::GET_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Decision::root() . '/bin/{bin}', 20 | RouteInterface::ARGS_KEY => ['bin'], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Integration.php: -------------------------------------------------------------------------------- 1 | RouteInterface::GET_METHOD, 18 | RouteInterface::ENDPOINT_KEY => Integration::root() . '/payment_session_timeout', 19 | RouteInterface::PARAMS_KEY => [] ]; 20 | } 21 | 22 | public static function updatePaymentSessionTimeout() 23 | { 24 | return [ 25 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 26 | RouteInterface::ENDPOINT_KEY => Integration::root() . '/payment_session_timeout', 27 | RouteInterface::PARAMS_KEY => ['timeout'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Invoice.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 18 | RouteInterface::ENDPOINT_KEY => Invoice::root(), 19 | RouteInterface::PARAMS_KEY => [ 20 | 'line_items', 21 | 'description', 22 | 'amount', 23 | 'customer', 24 | 'send_notification', 25 | 'tax', 26 | 'due_date', 27 | 'metadata', 28 | 'draft', 29 | 'currency', 30 | 'has_invoice', 31 | 'invoice_number', 32 | ], 33 | RouteInterface::REQUIRED_KEY => [ 34 | RouteInterface::PARAMS_KEY => [ 35 | 'customer', 36 | 'amount', 37 | 'due_date', 38 | ], 39 | ], 40 | ]; 41 | } 42 | 43 | public static function fetch() 44 | { 45 | return [ 46 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 47 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/{invoice_id_or_code}', 48 | RouteInterface::ARGS_KEY => ['invoice_id_or_code'], 49 | RouteInterface::REQUIRED_KEY => [RouteInterface::ARGS_KEY => ['invoice_id_or_code']], 50 | ]; 51 | } 52 | 53 | public static function getList() 54 | { 55 | return [ 56 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 57 | RouteInterface::ENDPOINT_KEY => Invoice::root(), 58 | RouteInterface::PARAMS_KEY => [ 59 | 'currency', 60 | 'customer', 'status', 'paid', 'include_archive', 61 | ], 62 | ]; 63 | } 64 | 65 | public static function verify() 66 | { 67 | return [ 68 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 69 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/verify/{invoice_id_or_code}', 70 | RouteInterface::ARGS_KEY => ['invoice_id_or_code'], 71 | RouteInterface::REQUIRED_KEY => [RouteInterface::ARGS_KEY => ['invoice_id_or_code']], 72 | ]; 73 | } 74 | 75 | public static function notify() 76 | { 77 | return [ 78 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 79 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/notify/{invoice_id_or_code}', 80 | ]; 81 | } 82 | 83 | public static function metrics() 84 | { 85 | return [ 86 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 87 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/totals', 88 | ]; 89 | } 90 | 91 | public static function finalize() 92 | { 93 | return [ 94 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 95 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/finalize/{invoice_id_or_code}', 96 | RouteInterface::ARGS_KEY => ['invoice_id_or_code'], 97 | RouteInterface::REQUIRED_KEY => [RouteInterface::ARGS_KEY => ['invoice_id_or_code']], 98 | ]; 99 | } 100 | 101 | public static function update() 102 | { 103 | return [ 104 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 105 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/update/{invoice_id_or_code}', 106 | RouteInterface::PARAMS_KEY => [ 107 | 'line_items', 108 | 'description', 109 | 'amount', 110 | 'customer', 111 | 'send_notification', 112 | 'tax', 113 | 'due_date', 114 | 'metadata', 115 | 'currency', 116 | ], 117 | ]; 118 | } 119 | 120 | public static function archive() 121 | { 122 | return [ 123 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 124 | RouteInterface::ENDPOINT_KEY => Invoice::root() . '/archive/{invoice_id_or_code}', 125 | ]; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Page.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Page::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'name', 'description', 22 | 'amount', 23 | ], 24 | ]; 25 | } 26 | 27 | public static function fetch() 28 | { 29 | return [ 30 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 31 | RouteInterface::ENDPOINT_KEY => Page::root() . '/{id}', 32 | RouteInterface::ARGS_KEY => ['id'], 33 | ]; 34 | } 35 | 36 | public static function getList() 37 | { 38 | return [ 39 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 40 | RouteInterface::ENDPOINT_KEY => Page::root(), 41 | ]; 42 | } 43 | 44 | public static function update() 45 | { 46 | return [ 47 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 48 | RouteInterface::ENDPOINT_KEY => Page::root() . '/{id}', 49 | RouteInterface::PARAMS_KEY => [ 50 | 'name', 51 | 'description', 52 | 'amount', 53 | 'active', 54 | ], 55 | RouteInterface::ARGS_KEY => ['id'], 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Plan.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Plan::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'name', 22 | 'description', 23 | 'amount', 24 | 'interval', 25 | 'send_invoices', 26 | 'send_sms', 27 | 'hosted_page', 28 | 'hosted_page_url', 29 | 'hosted_page_summary', 30 | 'currency', 31 | ], 32 | ]; 33 | } 34 | 35 | public static function fetch() 36 | { 37 | return [ 38 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 39 | RouteInterface::ENDPOINT_KEY => Plan::root() . '/{id}', 40 | RouteInterface::ARGS_KEY => ['id'], 41 | ]; 42 | } 43 | 44 | public static function getList() 45 | { 46 | return [ 47 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 48 | RouteInterface::ENDPOINT_KEY => Plan::root(), 49 | ]; 50 | } 51 | 52 | public static function update() 53 | { 54 | return [ 55 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 56 | RouteInterface::ENDPOINT_KEY => Plan::root() . '/{id}', 57 | RouteInterface::PARAMS_KEY => [ 58 | 'name', 59 | 'description', 60 | 'amount', 61 | 'interval', 62 | 'send_invoices', 63 | 'send_sms', 64 | 'hosted_page', 65 | 'hosted_page_url', 66 | 'hosted_page_summary', 67 | 'currency', 68 | ], 69 | RouteInterface::ARGS_KEY => ['id'], 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Settlement.php: -------------------------------------------------------------------------------- 1 | RouteInterface::GET_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Settlement::root(), 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Subaccount.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Subaccount::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'business_name', 'settlement_bank', 22 | 'account_number', 'percentage_charge', 23 | 'primary_contact_email', 'primary_contact_name', 24 | 'primary_contact_phone', 25 | 'metadata', 'settlement_schedule', 26 | ], 27 | ]; 28 | } 29 | 30 | public static function fetch() 31 | { 32 | return [ 33 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 34 | RouteInterface::ENDPOINT_KEY => Subaccount::root() . '/{id}', 35 | RouteInterface::ARGS_KEY => ['id'], 36 | ]; 37 | } 38 | 39 | public static function getList() 40 | { 41 | return [ 42 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 43 | RouteInterface::ENDPOINT_KEY => Subaccount::root(), 44 | ]; 45 | } 46 | 47 | public static function update() 48 | { 49 | return [ 50 | RouteInterface::METHOD_KEY => RouteInterface::PUT_METHOD, 51 | RouteInterface::ENDPOINT_KEY => Subaccount::root() . '/{id}', 52 | RouteInterface::PARAMS_KEY => [ 53 | 'business_name', 'settlement_bank', 54 | 'account_number', 'percentage_charge', 55 | 'primary_contact_email', 'primary_contact_name', 56 | 'primary_contact_phone', 57 | 'metadata', 'settlement_schedule', 58 | ], 59 | RouteInterface::ARGS_KEY => ['id'], 60 | ]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Subscription.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Subscription::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'customer', 22 | 'plan', 23 | 'authorization', 24 | ], 25 | ]; 26 | } 27 | 28 | public static function fetch() 29 | { 30 | return [ 31 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 32 | RouteInterface::ENDPOINT_KEY => Subscription::root() . '/{id}', 33 | RouteInterface::ARGS_KEY => ['id'], 34 | ]; 35 | } 36 | 37 | public static function getList() 38 | { 39 | return [ 40 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 41 | RouteInterface::ENDPOINT_KEY => Subscription::root(), 42 | ]; 43 | } 44 | 45 | public static function disable() 46 | { 47 | return [ 48 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 49 | RouteInterface::ENDPOINT_KEY => Subscription::root() . '/disable', 50 | RouteInterface::PARAMS_KEY => [ 51 | 'code', 52 | 'token', 53 | ], 54 | ]; 55 | } 56 | 57 | public static function enable() 58 | { 59 | return [ 60 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 61 | RouteInterface::ENDPOINT_KEY => Subscription::root() . '/enable', 62 | RouteInterface::PARAMS_KEY => [ 63 | 'code', 64 | 'token', 65 | ], 66 | ]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Transaction.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/initialize', 20 | RouteInterface::PARAMS_KEY => [ 21 | 'reference', 22 | 'callback_url', 23 | 'amount', 24 | 'email', 25 | 'plan', 26 | ], 27 | ]; 28 | } 29 | 30 | public static function charge() 31 | { 32 | return [ 33 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 34 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/charge_authorization', 35 | RouteInterface::PARAMS_KEY => [ 36 | 'reference', 37 | 'authorization_code', 38 | 'email', 39 | 'amount', 40 | ], 41 | ]; 42 | } 43 | 44 | public static function checkAuthorization() 45 | { 46 | return [ 47 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 48 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/check_authorization', 49 | RouteInterface::PARAMS_KEY => [ 50 | 'authorization_code', 51 | 'email', 52 | 'amount', 53 | ], 54 | ]; 55 | } 56 | 57 | public static function chargeAuthorization() 58 | { 59 | return Transaction::charge(); 60 | } 61 | 62 | public static function chargeToken() 63 | { 64 | trigger_error('This endpoint is deprecated!', E_USER_NOTICE); 65 | return [ 66 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 67 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/charge_token', 68 | RouteInterface::PARAMS_KEY => [ 69 | 'reference', 70 | 'token', 71 | 'email', 72 | 'amount', 73 | ], 74 | ]; 75 | } 76 | 77 | public static function fetch() 78 | { 79 | return [ 80 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 81 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/{id}', 82 | RouteInterface::ARGS_KEY => ['id'], 83 | ]; 84 | } 85 | 86 | public static function getList() 87 | { 88 | return [ 89 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 90 | RouteInterface::ENDPOINT_KEY => Transaction::root(), 91 | ]; 92 | } 93 | 94 | public static function export() 95 | { 96 | return [ 97 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 98 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/export', 99 | RouteInterface::PARAMS_KEY => [ 100 | 'from', 101 | 'to', 102 | 'settled', 103 | 'payment_page', 104 | ], 105 | ]; 106 | } 107 | 108 | public static function totals() 109 | { 110 | return [ 111 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 112 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/totals', 113 | ]; 114 | } 115 | 116 | public static function verify() 117 | { 118 | return [ 119 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 120 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/verify/{reference}', 121 | RouteInterface::ARGS_KEY => ['reference'], 122 | ]; 123 | } 124 | 125 | public static function verifyAccessCode() 126 | { 127 | return [ 128 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 129 | RouteInterface::ENDPOINT_KEY => Transaction::root() . '/verify_access_code/{access_code}', 130 | RouteInterface::ARGS_KEY => ['access_code'], 131 | ]; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Transfer.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Transfer::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'source', 22 | 'amount', 23 | 'currency', 24 | 'reason', 25 | 'recipient', 26 | ], 27 | ]; 28 | } 29 | 30 | public static function finalizeTransfer() 31 | { 32 | return [ 33 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 34 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/finalize_transfer', 35 | RouteInterface::PARAMS_KEY => [ 36 | 'reference', 37 | 'transfer_code', 38 | 'otp', 39 | ], 40 | ]; 41 | } 42 | 43 | public static function resendOtp() 44 | { 45 | return [ 46 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 47 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/resend_otp', 48 | RouteInterface::PARAMS_KEY => [ 49 | 'transfer_code', 50 | 'reason', 51 | ], 52 | ]; 53 | } 54 | 55 | public static function disableOtp() 56 | { 57 | return [ 58 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 59 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/disable_otp', 60 | ]; 61 | } 62 | 63 | public static function enableOtp() 64 | { 65 | return [ 66 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 67 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/enable_otp', 68 | ]; 69 | } 70 | 71 | public static function disableOtpFinalize() 72 | { 73 | return [ 74 | RouteInterface::METHOD_KEY => RouteInterface::POST_METHOD, 75 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/disable_otp_finalize', 76 | RouteInterface::PARAMS_KEY => ['otp'], 77 | ]; 78 | } 79 | 80 | public static function fetch() 81 | { 82 | return [ 83 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 84 | RouteInterface::ENDPOINT_KEY => Transfer::root() . '/{id}', 85 | RouteInterface::ARGS_KEY => ['id'], 86 | ]; 87 | } 88 | 89 | public static function getList() 90 | { 91 | return [ 92 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 93 | RouteInterface::ENDPOINT_KEY => Transfer::root(), 94 | ]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Paystack/Routes/Transferrecipient.php: -------------------------------------------------------------------------------- 1 | RouteInterface::POST_METHOD, 19 | RouteInterface::ENDPOINT_KEY => Transferrecipient::root(), 20 | RouteInterface::PARAMS_KEY => [ 21 | 'type', 22 | 'name', 23 | 'description', 24 | 'metadata', 25 | 'bank_code', 26 | 'currency', 27 | 'account_number', 28 | ], 29 | RouteInterface::REQUIRED_KEY => [ 30 | RouteInterface::PARAMS_KEY => [ 31 | 'type', 32 | 'name', 33 | 'bank_code', 34 | 'account_number', 35 | ], 36 | ], 37 | ]; 38 | } 39 | 40 | public static function getList() 41 | { 42 | return [ 43 | RouteInterface::METHOD_KEY => RouteInterface::GET_METHOD, 44 | RouteInterface::ENDPOINT_KEY => Transferrecipient::root(), 45 | RouteInterface::PARAMS_KEY => [ 46 | 'perPage', 47 | 'page', 48 | ], 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 |