diff --git a/.distignore b/.distignore index dbd5845..d1908d7 100644 --- a/.distignore +++ b/.distignore @@ -2,4 +2,6 @@ docker-compose.yml .gitignore .github -.env.example \ No newline at end of file +.env.example +.docker +Makefile diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 0000000..2e89b0e --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,17 @@ +services: + web: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + depends_on: + - app + app: + build: + context: . + dockerfile: ./php/Dockerfile + db: + image: mysql:5.7 + volumes: + - ./mysql:/var/lib/mysql \ No newline at end of file diff --git a/.docker/nginx/default.conf b/.docker/nginx/default.conf new file mode 100644 index 0000000..deea96f --- /dev/null +++ b/.docker/nginx/default.conf @@ -0,0 +1,18 @@ +server { + listen 80; + server_name localhost; + root /var/www/html/public; + index index.php; + + location ~ \.php$ { + fastcgi_pass app:9000; + fastcgi_index index.php; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } +} \ No newline at end of file diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile new file mode 100644 index 0000000..0b11c16 --- /dev/null +++ b/.docker/php/Dockerfile @@ -0,0 +1,52 @@ +FROM php:8.1-fpm-alpine as app + +# Useful PHP extension installer image, copy binary into your container +COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ + +# Install php extensions +# exit on errors, exit on unset variables, print every command as it is executed +RUN set -eux; \ + install-php-extensions pdo pdo_mysql; + +# RUN docker-php-ext-install pdo pdo_mysql + +# allow super user - set this if you use Composer as a +# super user at all times like in docker containers +ENV COMPOSER_ALLOW_SUPERUSER=1 + +# obtain composer using multi-stage build +# https://docs.docker.com/build/building/multi-stage/ +COPY --from=composer:2.4 /usr/bin/composer /usr/bin/composer + +#Here, we are copying only composer.json and composer.lock (instead of copying the entire source) +# right before doing composer install. +# This is enough to take advantage of docker cache and composer install will +# be executed only when composer.json or composer.lock have indeed changed!- +# https://medium.com/@softius/faster-docker-builds-with-composer-install-b4d2b15d0fff +COPY ./app/composer.* ./ + +# install +RUN composer install --prefer-dist --no-dev --no-scripts --no-progress --no-interaction + +# copy application files to the working directory +COPY ./app . + +# run composer dump-autoload --optimize +RUN composer dump-autoload --optimize + +# Dev image +# This stage is meant to be target-built into a separate image +# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage +# https://docs.docker.com/compose/compose-file/#target +FROM app as app_dev + +# Xdebug has different modes / functionalities. We can default to 'off' and set to 'debug' +# when we run docker compose up if we need it +ENV XDEBUG_MODE=off + +# Copy xdebug config file into container +COPY ./php/conf.d/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini + +# Install xdebug +RUN set -eux; \ + install-php-extensions xdebug \ No newline at end of file diff --git a/.docker/php/conf.d/xdebug.ini b/.docker/php/conf.d/xdebug.ini new file mode 100644 index 0000000..0b4327e --- /dev/null +++ b/.docker/php/conf.d/xdebug.ini @@ -0,0 +1 @@ +xdebug.client_host = 'host.docker.internal' \ No newline at end of file diff --git a/.github/workflows/change-review.yml b/.github/workflows/change-review.yml new file mode 100644 index 0000000..2dc7be7 --- /dev/null +++ b/.github/workflows/change-review.yml @@ -0,0 +1,78 @@ +name: Review changes on Dev (Commits/PRs) + +on: + push: + branches: ["dev"] + pull_request: + types: + - opened + +jobs: + code-check: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [7.4, 8.1, 8.2] + + env: + XDEBUG_MODE: coverage + PUBLIC_KEY: ${{ secrets.PUBLIC_KEY }} + SECRET_KEY: ${{ secrets.SECRET_KEY }} + ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} + ENV: ${{ secrets.ENV }} + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: xdebug + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: 'Create env file' + run: | + touch .env + echo PUBLIC_KEY=${PUBLIC_KEY} >> .env + echo SECRET_KEY=${SECRET_KEY} >> .env + echo ENCRYPTION_KEY=${ENCRYPTION_KEY} >> .env + echo ENV=${ENV} >> .env + ls -a ${{ github.workspace }} + + - name: run unit tests and coverage scan + run: ./vendor/bin/pest --coverage --min=20 --coverage-clover ./coverage.xml + + - name: Upload to Codecov + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODE_COV_TOKEN }} + files: ./coverage.xml + verbose: true + + - name: push build status to Slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + fields: repo,message,commit,author,action,eventName,ref,workflow,job,took,pullRequest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + MATRIX_CONTEXT: ${{ toJson(matrix) }} + if: always() diff --git a/.github/workflows/package-publish.yml b/.github/workflows/package-publish.yml new file mode 100644 index 0000000..5d005d2 --- /dev/null +++ b/.github/workflows/package-publish.yml @@ -0,0 +1,46 @@ +name: Pre-release + +on: + release: + types: [created] + +jobs: + check-docs-update: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [7.4, 8.1, 8.2] + + steps: + - uses: actions/checkout@v2 + - uses: technote-space/get-diff-action@v6 + with: + PATTERNS: | + +(documentation)/*.md + *.md + CHANGE*.md + FILES: | + CHANGELOG.md + + - name: log git diff + run: | + echo ${{ env.GIT_DIFF }} + echo ${{ env.MATCHED_FILES }} + echo ${{ env.GIT_DIFF_FILTERED }} + + - name: Check if README.md or Doc/** is updated else exit + if: (env.GIT_DIFF == '') + run: | + echo Update documentation files and README.md before push + exit 1 + + - name: push build status to Slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + fields: repo,message,commit,author,action,eventName,ref,workflow,job,took,pullRequest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 596af21..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: PHP Test - -on: - push: - branches: [ "development" ] - pull_request: - branches: [ "development" ] -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - env: - PUBLIC_KEY: ${{ secrets.PUBLIC_KEY }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} - ENV: ${{ secrets.ENV }} - - steps: - - uses: actions/checkout@v3 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: PHPStan analysis - run: vendor/bin/phpstan analyse tests --no-progress --no-interaction --error-format=table - - - name: Run test suite - run: ./vendor/bin/pest diff --git a/.gitignore b/.gitignore index 3011762..d1eb24c 100644 --- a/.gitignore +++ b/.gitignore @@ -195,4 +195,7 @@ examples/*.log examples/endpoint/*.log example.php .phpunit.result.cache +.phpunit.cache +coverage.xml .env.local +.php-cs-fixer.cache \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8a0e670 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +## 1.0.4 | 2022-11-06 + +This release adds support for 7.4 and above. a new workflow for old and new tests. + +### Dependency updates and bugfixes + +- [ADDED] Support for PHP 7.4 and above +- [ADDED] New workflow for old and new tests \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0fa8af9 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +.PHONY: init +test: + @echo "Installing dependencies..." + @composer install + @echo "Installing dependencies... Done" + @./vendor/bin/pest --coverage --min=0 --coverage-clover ./coverage.xml + + diff --git a/README.md b/README.md index bce45f0..67b1a87 100644 --- a/README.md +++ b/README.md @@ -40,34 +40,37 @@ Available features include: ## Requirements 1. Flutterwave for business [API Keys](https://developer.flutterwave.com/docs/integration-guides/authentication) -2. Acceptable PHP versions: >= 5.4.0 +2. Acceptable PHP versions: >= 7.4.0. for older versions of PHP use the [Legacy Branch]( https://github.com/Flutterwave/PHP-v3/tree/legacy ) ## Installation -The vendor folder is committed into the project to allow easy installation for those who do not have composer installed. -It is recommended to update the project dependencies using: +### Installation via Composer. +To install the package via Composer, run the following command. ```shell -$ composer require flutterwavedev/flutterwave-v3 +composer require flutterwavedev/flutterwave-v3 ``` ## Initialization -Create a .env file and follow the format of the .env.example file -Save your PUBLIC_KEY, SECRET_KEY, ENV in the .env file +Create a .env file and follow the format of the `.env.example` file +Save your PUBLIC_KEY, SECRET_KEY, ENV in the `.env` file -```env - -PUBLIC_KEY="****YOUR**PUBLIC**KEY****" // can be gotten from the dashboard -SECRET_KEY="****YOUR**SECRET**KEY****" // can be gotten from the dashboard -ENCRYPTION_KEY="Encryption key" -ENV="development/production" +```bash +cp .env.example .env +``` +Your `.env` file should look this. +```env +PUBLIC_KEY=FLWSECK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X +SECRET_KEY=FLWPUBK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X +ENCRYPTION_KEY=FLWSECK_XXXXXXXXXXXXXXXX +ENV='staging/production' ``` @@ -75,226 +78,87 @@ ENV="development/production" ## Usage -### Card Charge -This is used to facilitate card transactions. - -Edit the `paymentForm.php` and `processPayment.php` files to suit your purpose. Both files are well documented. - -Simply redirect to the `paymentForm.php` file on your browser to process a payment. - -In this implementation, we are expecting a form encoded POST request to this script. -The request will contain the following parameters. - -- payment_method `Can be card, account, both` -- description `Your transaction description` -- logo `Your logo url` -- title `Your transaction title` -- country `Your transaction country` -- currency `Your transaction currency` -- email `Your customer's email` -- firstname `Your customer's first name` -- lastname `Your customer's last name` -- phonenumber `Your customer's phonenumber` -- pay_button_text `The payment button text you prefer` -- ref `Your transaction reference. It must be unique per transaction. By default, the Rave class generates a unique transaction reference for each transaction. Pass this parameter only if you uncommented the related section in the script below.` - -```php -status === 'successful') { - if ($transactionData->currency == $_SESSION['currency'] && $transactionData->amount == $_SESSION['amount']) { - - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['successurl'], array('event' => 'successful'))); - $_SESSION = array(); - session_destroy(); - } - } else { - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], array('event' => 'suspicious'))); - $_SESSION = array(); - session_destroy(); - } - } - } else { - $this->onFailure($transactionData); - } - } +declare(strict_types=1); - /** - * This is called only when a transaction failed - * */ - function onFailure($transactionData) { - // Get the transaction from your DB using the transaction reference (txref) - // Update the db transaction record (includeing parameters that didn't exist before the transaction is completed. for audit purpose) - // You can also redirect to your failure page from here - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], array('event' => 'failed'))); - $_SESSION = array(); - session_destroy(); - } - } +# if vendor file is not present, notify developer to run composer install. +require __DIR__.'/vendor/autoload.php'; - /** - * This is called when a transaction is requeryed from the payment gateway - * */ - function onRequery($transactionReference) { - // Do something, anything! - } +use Flutterwave\Controller\PaymentController; +use Flutterwave\EventHandlers\ModalEventHandler as PaymentHandler; +use Flutterwave\Flutterwave; +use Flutterwave\Library\Modal; - /** - * This is called a transaction requery returns with an error - * */ - function onRequeryError($requeryResponse) { - echo 'the transaction was not found'; - } +# start a session. +session_start(); - /** - * This is called when a transaction is canceled by the user - * */ - function onCancel($transactionReference) { - // Do something, anything! - // Note: Somethings a payment can be successful, before a user clicks the cancel button so proceed with caution - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], array('event' => 'canceled'))); - $_SESSION = array(); - session_destroy(); - } - } +try { + Flutterwave::bootstrap(); + $customHandler = new PaymentHandler(); + $client = new Flutterwave(); + $modalType = Modal::POPUP; // Modal::POPUP or Modal::STANDARD + $controller = new PaymentController( $client, $customHandler, $modalType ); +} catch(\Exception $e ) { + echo $e->getMessage(); +} - /** - * This is called when a transaction doesn't return with a success or a failure response. This can be a timedout transaction on the Rave server or an abandoned transaction by the customer. - * */ - function onTimeout($transactionReference, $data) { - // Get the transaction from your DB using the transaction reference (txref) - // Queue it for requery. Preferably using a queue system. The requery should be about 15 minutes after. - // Ask the customer to contact your support and you should escalate this issue to the flutterwave support team. Send this as an email and as a notification on the page. just incase the page timesout or disconnects - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], array('event' => 'timedout'))); - $_SESSION = array(); - session_destroy(); - } +if ($_SERVER["REQUEST_METHOD"] === "POST") { + $request = $_REQUEST; + $request['redirect_url'] = $_SERVER['HTTP_ORIGIN'] . $_SERVER['REQUEST_URI']; + try { + $controller->process( $request ); + } catch(\Exception $e) { + echo $e->getMessage(); } } -if (isset($postData['amount'])) { - // Make payment - $payment - ->eventHandler(new myEventHandler) - ->setAmount($postData['amount']) - ->setPaymentOptions($postData['payment_options']) // value can be card, account or both - ->setDescription($postData['description']) - ->setLogo($postData['logo']) - ->setTitle($postData['title']) - ->setCountry($postData['country']) - ->setCurrency($postData['currency']) - ->setEmail($postData['email']) - ->setFirstname($postData['firstname']) - ->setLastname($postData['lastname']) - ->setPhoneNumber($postData['phonenumber']) - ->setPayButtonText($postData['pay_button_text']) - ->setRedirectUrl($URL) - // ->setMetaData(array('metaname' => 'SomeDataName', 'metavalue' => 'SomeValue')) // can be called multiple times. Uncomment this to add meta datas - // ->setMetaData(array('metaname' => 'SomeOtherDataName', 'metavalue' => 'SomeOtherValue')) // can be called multiple times. Uncomment this to add meta datas - ->initialize(); +$request = $_GET; +# Confirming Payment. +if(isset($request['tx_ref'])) { + $controller->callback( $request ); } else { - if (isset($getData['cancelled'])) { - // Handle canceled payments - $payment - ->eventHandler(new myEventHandler) - ->paymentCanceled($getData['cancel_ref']); - } elseif (isset($getData['tx_ref'])) { - // Handle completed payments - $payment->logger->notice('Payment completed. Now requerying payment.'); - $payment - ->eventHandler(new myEventHandler) - ->requeryTransaction($getData['transaction_id']); - } else { - $payment->logger->warning('Stop!!! Please pass the txref parameter!'); - echo 'Stop!!! Please pass the txref parameter!'; - } + } +exit(); + ```
@@ -302,11 +166,18 @@ if (isset($postData['amount'])) { Create a .env file and add the bootstrap method first before initiating a charge. ```php use \Flutterwave\Flutterwave; +use \Flutterwave\Helper\Config; # normal configuration -Flutterwave::bootstrap(); +Flutterwave::bootstrap(); # this will use the default configuration set in .env # for a custom configuration -# your config must implement Flutterwave\Contract\ConfigInterface +# your config must implement Flutterwave\Contract\ConfigInterface +$myConfig = Config::setUp( + 'FLWSECK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X', + 'FLWPUBK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X', + 'FLWSECK_XXXXXXXXXXXXXXXX', + 'staging' +); Flutterwave::bootstrap($myConfig); ``` @@ -406,8 +277,8 @@ $data = [ $cardpayment = \Flutterwave\Flutterwave::create("card"); $customerObj = $cardpayment->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "ola2fhahfj@gmail.com", + "phone" => "+234900154861" ]); $data['customer'] = $customerObj; $payload = $cardpayment->payload->create($data); @@ -522,8 +393,8 @@ $data = [ $service = new Transfer(); $customerObj = $service->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "38djsdjfjc954@gmail.com", + "phone" => "+234900085861" ]); $data['customer'] = $customerObj; $payload = $service->payload->create($data); @@ -702,6 +573,33 @@ $response = $service->create($payload); ```
+### Enaira + +```php +use Flutterwave\Util\Currency; + +$data = [ + "amount" => 2000, + "is_token" => 1, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" +]; + +$payment = \Flutterwave\Flutterwave::create("enaira"); +$customerObj = $payment->customer->create([ + "full_name" => "Olaobaju Jesulayomi Abraham", + "email" => "developers@flutterwavego.com", + "phone" => "+2349060085861" +]); + +$data['customer'] = $customerObj; +$payload = $payment->payload->create($data); +$result = $payment->initiate($payload); +``` + +
+ ### Tokenized Charge Once the charge and validation process is complete for the first charge on the card, you can make use of the token for subsequent charges. @@ -723,8 +621,8 @@ $data['redirectUrl'] = "http://{$_SERVER['HTTP_HOST']}/examples/endpoint/verify. $customerObj = $tokenpayment->customer->create([ "full_name" => "Olaobaju Jesulayomi Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "ola3785yfhf@gmail.com", + "phone" => "+2349062947561" ]); $data['customer'] = $customerObj; $tokenpayment = \Flutterwave\Flutterwave::create("tokenize"); diff --git a/composer.json b/composer.json index d289016..a2012a4 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,12 @@ "monolog/monolog": "^2.0 || ^3.0", "vlucas/phpdotenv": "^2.5 || ^3.0 || ^5.0", "ext-json": "*", + "ext-curl": "*", + "ext-openssl": "*", "guzzlehttp/guzzle": "^7.5", - "psr/http-client": "^1.0" + "psr/http-client": "^1.0", + "php-http/guzzle7-adapter": "^1.0", + "composer/ca-bundle": "^1.3" }, "require-dev": { "phpunit/phpunit": ">=6.0", @@ -26,7 +30,11 @@ "symfony/var-dumper": "5.4.13", "phpstan/phpstan": "^1.9", "pestphp/pest": "^1.22", - "nunomaduro/phpinsights": "^2.6" + "nunomaduro/phpinsights": "^2.6", + "eloquent/liberator": "^3.0", + "squizlabs/php_codesniffer": "3.*", + "dg/bypass-finals": "^1.4", + "phpbench/phpbench": "^1.2" }, "license": "MIT", "authors": [ @@ -40,5 +48,25 @@ "pestphp/pest-plugin": true, "dealerdirect/phpcodesniffer-composer-installer": true } + }, + "scripts": { + "php-insight": [ + "./vendor/bin/phpinsights analyse src" + ], + "php-insight-fix": [ + "vendor/bin/phpinsights fix src" + ], + "phpcs": [ + "./vendor/bin/phpcs" + ], + "phpcbf": [ + "./vendor/bin/phpcbf " + ], + "test": [ + "./vendor/bin/pest" + ], + "pest-filter": [ + "./vendor/bin/pest --filter" + ] } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 869968b..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - web: - image: nginx - ports: - - "80:80" - volumes: - - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf - depends_on: - - app - db: - image: mysql:5.7 - volumes: - - ./docker/mysql:/var/lib/mysql \ No newline at end of file diff --git a/examples/card.php b/examples/card.php index 653926c..3141857 100644 --- a/examples/card.php +++ b/examples/card.php @@ -41,8 +41,8 @@ $customerObj = $cardpayment->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "ol3746ydgsbc@gmail.com", + "phone" => "+2349035462461" ]); $data['customer'] = $customerObj; diff --git a/examples/endpoint/validate.php b/examples/endpoint/validate.php index b1065fe..f44d3d0 100644 --- a/examples/endpoint/validate.php +++ b/examples/endpoint/validate.php @@ -23,7 +23,7 @@ if ($res->status === 'success') { echo "Your payment status: " . $res->processor_response; } - } catch (\Unirest\Exception $e) { + } catch (\Psr\Http\Client\ClientExceptionInterface $e) { echo "error: ". $e->getMessage(); } diff --git a/examples/preauth.php b/examples/preauth.php index 63eaea1..9c5e55a 100644 --- a/examples/preauth.php +++ b/examples/preauth.php @@ -39,9 +39,9 @@ $data['redirectUrl'] = "http://{$_SERVER['HTTP_HOST']}/examples/endpoint/verify.php?tx_ref={$data['tx_ref']}"; $customerObj = $preauthpayment->customer->create([ - "full_name" => "Olaobaju Jesulayomi Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "full_name" => "Jack Logan Hugh", + "email" => "Jhughck@gmail.com", + "phone" => "+2349062919861" ]); $data['customer'] = $customerObj; diff --git a/examples/tokenized.php b/examples/tokenized.php index 0d9ab61..9b4b182 100644 --- a/examples/tokenized.php +++ b/examples/tokenized.php @@ -27,8 +27,8 @@ $customerObj = $tokenpayment->customer->create([ "full_name" => "Olaobaju Jesulayomi Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "olr75756uruf@gmail.com", + "phone" => "+2349067263131" ]); $data['customer'] = $customerObj; diff --git a/paymentForm.php b/paymentForm.php index 5bd99ab..34bb9d5 100644 --- a/paymentForm.php +++ b/paymentForm.php @@ -24,31 +24,28 @@
- - - + + - - - - - - - - + + + + + + - + - + - + - + - +
diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ec3a541..fc42dbe 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ -tests - + + + ./src + + + + + \ No newline at end of file diff --git a/processPayment.php b/processPayment.php index a5da1a5..622ad36 100644 --- a/processPayment.php +++ b/processPayment.php @@ -2,196 +2,42 @@ declare(strict_types=1); +# if vendor file is not present, notify developer to run composer install. require __DIR__.'/vendor/autoload.php'; -session_start(); - -use Flutterwave\EventHandlers\EventHandlerInterface; +use Flutterwave\Controller\PaymentController; +use Flutterwave\EventHandlers\ModalEventHandler as PaymentHandler; use Flutterwave\Flutterwave; +use Flutterwave\Library\Modal; -Flutterwave::bootstrap(); - -$URL = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; -$getData = $_GET; -$postData = $_POST; -$publicKey = $_SERVER['PUBLIC_KEY']; -$secretKey = $_SERVER['SECRET_KEY']; -if (isset($postData['successurl']) && isset($postData['failureurl'])) { - $success_url = $postData['successurl']; - $failure_url = $postData['failureurl']; -} - -$env = $_SERVER['ENV']; - -if (isset($postData['amount'])) { - $_SESSION['publicKey'] = $publicKey; - $_SESSION['secretKey'] = $secretKey; - $_SESSION['env'] = $env; - $_SESSION['successurl'] = $success_url ?? null; - $_SESSION['failureurl'] = $failure_url ?? null; - $_SESSION['currency'] = $postData['currency']; - $_SESSION['amount'] = $postData['amount']; -} - -$prefix = 'RV'; // Change this to the name of your business or app -$overrideRef = false; - -// Uncomment here to enforce the useage of your own ref else a ref will be generated for you automatically -if (isset($postData['ref'])) { - $prefix = $postData['ref']; - $overrideRef = true; -} - -$payment = new Flutterwave($prefix, $overrideRef); +# start a session. +session_start(); -function getURL($url, $data = []): string -{ - $urlArr = explode('?', $url); - $params = array_merge($_GET, $data); - $new_query_string = http_build_query($params) . '&' . $urlArr[1]; - return $urlArr[0] . '?' . $new_query_string; +try { + Flutterwave::bootstrap(); + $customHandler = new PaymentHandler(); + $client = new Flutterwave(); + $modalType = Modal::POPUP; // Modal::POPUP or Modal::STANDARD + $controller = new PaymentController( $client, $customHandler, $modalType ); +} catch(\Exception $e ) { + echo $e->getMessage(); } -// This is where you set how you want to handle the transaction at different stages -class myEventHandler implements EventHandlerInterface -{ - /** - * This is called when the Rave class is initialized - * */ - public function onInit($initializationData): void - { - // Save the transaction to your DB. - } - - /** - * This is called only when a transaction is successful - * */ - public function onSuccessful($transactionData): void - { - // Get the transaction from your DB using the transaction reference (txref) - // Check if you have previously given value for the transaction. If you have, redirect to your successpage else, continue - // Comfirm that the transaction is successful - // Confirm that the chargecode is 00 or 0 - // Confirm that the currency on your db transaction is equal to the returned currency - // Confirm that the db transaction amount is equal to the returned amount - // Update the db transaction record (includeing parameters that didn't exist before the transaction is completed. for audit purpose) - // Give value for the transaction - // Update the transaction to note that you have given value for the transaction - // You can also redirect to your success page from here - if ($transactionData->status === 'successful') { - if ($transactionData->currency === $_SESSION['currency'] && $transactionData->amount === $_SESSION['amount']) { - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['successurl'], ['event' => 'successful'])); - $_SESSION = []; - session_destroy(); - } - } else { - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], ['event' => 'suspicious'])); - $_SESSION = []; - session_destroy(); - } - } - } else { - $this->onFailure($transactionData); - } - } - - /** - * This is called only when a transaction failed - * */ - public function onFailure($transactionData): void - { - // Get the transaction from your DB using the transaction reference (txref) - // Update the db transaction record (includeing parameters that didn't exist before the transaction is completed. for audit purpose) - // You can also redirect to your failure page from here - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], ['event' => 'failed'])); - $_SESSION = []; - session_destroy(); - } - } - - /** - * This is called when a transaction is requeryed from the payment gateway - * */ - public function onRequery($transactionReference): void - { - // Do something, anything! - } - - /** - * This is called a transaction requery returns with an error - * */ - public function onRequeryError($requeryResponse): void - { - echo 'the transaction was not found'; - } - - /** - * This is called when a transaction is canceled by the user - * */ - public function onCancel($transactionReference): void - { - // Do something, anything! - // Note: Something's a payment can be successful, before a user clicks the cancel button so proceed with caution - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], ['event' => 'canceled'])); - $_SESSION = []; - session_destroy(); - } - } - - /** - * This is called when a transaction doesn't return with a success or a failure response. This can be a timedout transaction on the Rave server or an abandoned transaction by the customer. - * */ - public function onTimeout($transactionReference, $data): void - { - // Get the transaction from your DB using the transaction reference (txref) - // Queue it for requery. Preferably using a queue system. The requery should be about 15 minutes after. - // Ask the customer to contact your support, and you should escalate this issue to the flutterwave support team. Send this as an email and as a notification on the page. just incase the page timesout or disconnects - if ($_SESSION['publicKey']) { - header('Location: ' . getURL($_SESSION['failureurl'], ['event' => 'timedout'])); - $_SESSION = []; - session_destroy(); - } +if ($_SERVER["REQUEST_METHOD"] === "POST") { + $request = $_REQUEST; + $request['redirect_url'] = $_SERVER['HTTP_ORIGIN'] . $_SERVER['REQUEST_URI']; + try { + $controller->process( $request ); + } catch(\Exception $e) { + echo $e->getMessage(); } } -if (isset($postData['amount'])) { - // Make payment - $payment - ->eventHandler(new myEventHandler()) - ->setAmount($postData['amount']) - ->setPaymentOptions($postData['payment_options']) // value can be a card, account or both - ->setDescription($postData['description']) - ->setLogo($postData['logo']) - ->setTitle($postData['title']) - ->setCountry($postData['country']) - ->setCurrency($postData['currency']) - ->setEmail($postData['email']) - ->setFirstname($postData['firstname']) - ->setLastname($postData['lastname']) - ->setPhoneNumber($postData['phonenumber']) - ->setPayButtonText($postData['pay_button_text']) - ->setRedirectUrl($URL) - // ->setMetaData(array('metaname' => 'SomeDataName', 'metavalue' => 'SomeValue')) // can be called multiple times. Uncomment this to add meta datas - // ->setMetaData(array('metaname' => 'SomeOtherDataName', 'metavalue' => 'SomeOtherValue')) // can be called multiple times. Uncomment this to add meta datas - ->initialize(); +$request = $_GET; +# Confirming Payment. +if(isset($request['tx_ref'])) { + $controller->callback( $request ); } else { - if (isset($getData['cancelled'])) { - // Handle canceled payments - $payment - ->eventHandler(new myEventHandler()) - ->paymentCanceled($getData['cancel_ref']); - } elseif (isset($getData['tx_ref'])) { - // Handle completed payments - $payment->logger->notice('Payment completed. Now requerying payment.'); - $payment - ->eventHandler(new myEventHandler()) - ->requeryTransaction($getData['transaction_id']); - } else { - $payment->logger->warning('Stop!!! Please pass the txref parameter!'); - echo 'Stop!!! Please pass the txref parameter!'; - } + } +exit(); diff --git a/setup.php b/setup.php index b94f612..1c5b813 100644 --- a/setup.php +++ b/setup.php @@ -3,7 +3,15 @@ use Flutterwave\Helper; use Dotenv\Dotenv; -$dotenv = Dotenv::createImmutable(__DIR__."/../../../"); +$flutterwave_installation = 'composer'; + +if( !file_exists( '.env' )) { + $dotenv = Dotenv::createImmutable(__DIR__."/../../../"); # on the event that the package is install via composer. +} else { + $flutterwave_installation = "manual"; + $dotenv = Dotenv::createImmutable(__DIR__); # on the event that the package is forked or donwload directly from Github. +} + $dotenv->safeLoad(); //check if the current version of php is compatible @@ -13,14 +21,14 @@ exit; } -// check for required key in SERVER super global +// check for required key in ENV super global $flutterwaveKeys = ["SECRET_KEY","PUBLIC_KEY","ENV", "ENCRYPTION_KEY"]; asort($flutterwaveKeys); try{ foreach($flutterwaveKeys as $key) { - if(!array_key_exists($key, $_SERVER)) + if( empty( $_ENV[ $key ] ) ) { throw new InvalidArgumentException("$key environment variable missing."); } @@ -31,4 +39,11 @@ echo "
Kindly create a .env in the project root and add the required environment variables."; exit; -} \ No newline at end of file +} + +$keys = [ + 'SECRET_KEY' => $_ENV['SECRET_KEY'], + 'PUBLIC_KEY' => $_ENV['PUBLIC_KEY'], + 'ENV' => $_ENV['ENV'], + 'ENCRYPTION_KEY' => $_ENV['ENCRYPTION_KEY'] +]; \ No newline at end of file diff --git a/src/AbstractPayment.php b/src/AbstractPayment.php index 032b4dc..b067c6a 100644 --- a/src/AbstractPayment.php +++ b/src/AbstractPayment.php @@ -6,19 +6,22 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventHandlerInterface; +use Flutterwave\Helper\EnvVariables; use Flutterwave\Traits\ApiOperations as Api; use Flutterwave\Traits\PayloadOperations as Payload; use Psr\Log\LoggerInterface; abstract class AbstractPayment { - use Api\Post, Api\Get, Payload\Prepare; + use Api\Post; + use Api\Get; + use Payload\Prepare; public string $secretKey; public string $txref; public $type; public LoggerInterface $logger; -// protected ?string $integrityHash = null; + // protected ?string $integrityHash = null; protected string $payButtonText = 'Proceed with Payment'; protected string $redirectUrl; protected array $meta = []; @@ -52,12 +55,17 @@ abstract class AbstractPayment //EndPoints protected string $end_point; protected string $flwRef; - protected static ConfigInterface $config; + protected static ?ConfigInterface $config = null; - public function __construct(string $prefix, bool $overrideRefWithPrefix) + public function __construct() { - $this->transactionPrefix = $overrideRefWithPrefix ? $prefix : self::$config::DEFAULT_PREFIX . '_'; - $this->baseUrl = self::$config::BASE_URL; + $this->transactionPrefix = self::$config::DEFAULT_PREFIX . '_'; + $this->baseUrl = EnvVariables::BASE_URL; + } + + public function getConfig() + { + return self::$config; } abstract public function initialize(): void; diff --git a/src/Config/AbstractConfig.php b/src/Config/AbstractConfig.php new file mode 100644 index 0000000..6a34aad --- /dev/null +++ b/src/Config/AbstractConfig.php @@ -0,0 +1,83 @@ +secret = $secret_key; + $this->public = $public_key; + $this->enc = $encrypt_key; + $this->env = $env; + + $this->http = new Client( + [ + 'base_uri' => EnvVariables::BASE_URL, + 'timeout' => 60, + RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() + ] + ); + + $log = new Logger('Flutterwave/PHP'); + $this->logger = $log; + } + + abstract public static function setUp( + string $secretKey, + string $publicKey, + string $enc, + string $env + ): ConfigInterface; + + public function getHttp(): ClientInterface + { + return $this->http; + } + + public function getLoggerInstance(): LoggerInterface + { + return $this->logger; + } + + abstract public function getEncryptkey(): string; + + abstract public function getPublicKey(): string; + + abstract public function getSecretKey(): string; + + abstract public function getEnv(): string; + + public static function getDefaultTransactionPrefix(): string + { + return self::DEFAULT_PREFIX; + } +} diff --git a/src/Config/ForkConfig.php b/src/Config/ForkConfig.php new file mode 100644 index 0000000..f9983ec --- /dev/null +++ b/src/Config/ForkConfig.php @@ -0,0 +1,55 @@ +logger->pushHandler(new RotatingFileHandler(__DIR__ . "/../../" . self::LOG_FILE_NAME, 90)); + } + + public static function setUp(string $secretKey, string $publicKey, string $enc, string $env): ConfigInterface + { + if (is_null(self::$instance)) { + return new self($secretKey, $publicKey, $enc, $env); + } + return self::$instance; + } + + public function getEncryptkey(): string + { + return $this->enc; + } + + public function getPublicKey(): string + { + return $this->public; + } + + public function getSecretKey(): string + { + return $this->secret; + } + + public function getEnv(): string + { + return $this->env; + } +} diff --git a/src/Config/PackageConfig.php b/src/Config/PackageConfig.php new file mode 100644 index 0000000..e718437 --- /dev/null +++ b/src/Config/PackageConfig.php @@ -0,0 +1,56 @@ +logger->pushHandler(new RotatingFileHandler(__DIR__ . "../../../../../../" . self::LOG_FILE_NAME, 90)); + } + + public static function setUp(string $secretKey, string $publicKey, string $enc, string $env): ConfigInterface + { + + if (is_null(self::$instance)) { + return new self($secretKey, $publicKey, $enc, $env); + } + return self::$instance; + } + + public function getEncryptkey(): string + { + return $this->enc; + } + + public function getPublicKey(): string + { + return $this->public; + } + + public function getSecretKey(): string + { + return $this->secret; + } + + public function getEnv(): string + { + return $this->env; + } +} diff --git a/src/Contract/ConfigInterface.php b/src/Contract/ConfigInterface.php index 0e4c2d2..8fcaac6 100644 --- a/src/Contract/ConfigInterface.php +++ b/src/Contract/ConfigInterface.php @@ -4,12 +4,11 @@ namespace Flutterwave\Contract; -use GuzzleHttp\ClientInterface; +use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerInterface; interface ConfigInterface { - public static function setUp(string $secretKey, string $publicKey, string $enc, string $env): ConfigInterface; public function getHttp(): ClientInterface; @@ -21,8 +20,6 @@ public function getPublicKey(): string; public function getSecretKey(): string; - public static function getBaseUrl(): string; - public function getEnv(): string; public static function getDefaultTransactionPrefix(): string; diff --git a/src/Contract/CustomerInterface.php b/src/Contract/CustomerInterface.php index 38d5f92..cc90e41 100644 --- a/src/Contract/CustomerInterface.php +++ b/src/Contract/CustomerInterface.php @@ -4,7 +4,7 @@ namespace Flutterwave\Contract; -use Flutterwave\Customer; +use Flutterwave\Entities\Customer; interface CustomerInterface { diff --git a/src/Contract/EntityInterface.php b/src/Contract/EntityInterface.php new file mode 100644 index 0000000..875d75b --- /dev/null +++ b/src/Contract/EntityInterface.php @@ -0,0 +1,10 @@ + 'POST', + 'callback' => 'GET' + ]; + + public function __construct( + Flutterwave $client, + EventHandlerInterface $handler, + string $modalType + ) { + Flutterwave::bootstrap(); + $this->requestMethod = $this->getRequestMethod(); + $this->handler = $handler; + $this->client = $client; + $this->modalType = $modalType; + } + + private function getRequestMethod(): string + { + return ($_SERVER["REQUEST_METHOD"] === "POST") ? 'POST' : 'GET'; + } + + public function __call(string $name, array $args) + { + if ($this->routes[$name] !== $this->$requestMethod) { + // Todo: 404(); + echo "Unauthorized page!"; + } + call_user_method_array($name, $this, $args); + } + + private function handleSessionData( array $request ) + { + $_SESSION['success_url'] = $request['success_url']; + $_SESSION['failure_url'] = $request['failure_url']; + $_SESSION['currency'] = $request['currency']; + $_SESSION['amount'] = $request['amount']; + } + + public function process(array $request) + { + $this->handleSessionData($request); + + try { + $_SESSION['p'] = $this->client; + + if('inline' === $this->modalType ) { + echo $this->client + ->eventHandler($this->handler) + ->render(Modal::POPUP)->with($request)->getHtml(); + } else { + $paymentLink = $this->client + ->eventHandler($this->handler) + ->render(Modal::STANDARD)->with($request)->getUrl(); + header('Location: ' . $paymentLink); + } + + } catch (\Exception $e) { + echo $e->getMessage(); + } + } + + public function callback(array $request) + { + $tx_ref = $request['tx_ref']; + $status = $request['status']; + + if (empty($tx_ref)) { + session_destroy(); + } + + if (!isset($_SESSION['p'])) { + echo "session expired!. please refresh you browser."; + exit(); + } + + $payment = $_SESSION['p']; + + // $payment::setUp([ + // 'secret_key' => 'FLWSECK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X', + // 'public_key' => 'FLWPUBK_TEST-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-X', + // 'encryption_key' => 'FLWSECK_XXXXXXXXXXXXXXXX', + // 'environment' => 'staging' + // ]); + + $payment::bootstrap(); + + if ('cancelled' === $status) { + $payment + ->eventHandler($this->handler) + ->paymentCanceled($tx_ref); + } + + if ('successful' === $status && isset($request['transaction_id'])) { + $tx_id = $request['transaction_id']; + + if (empty($tx_id) && !empty($tx_ref)) { + // get tx_id with the transaction service. + $response = (new Transactions())->verifyWithTxref($tx_ref); + + if ('success' === $response->status) { + $tx_id = $response->data->id; + } + } + + $payment->logger->notice('Payment completed. Now requerying payment.'); + $payment + ->eventHandler($this->handler) + ->requeryTransaction($tx_id); + } + } +} diff --git a/src/Customer.php b/src/Customer.php index de4a426..e04453b 100644 --- a/src/Customer.php +++ b/src/Customer.php @@ -4,33 +4,37 @@ namespace Flutterwave; +/** + * Class Customer + * + * @package Flutterwave + * @deprecated Use Flutterwave\Entities\Customer instead. + */ class Customer { - private array $data = []; - + private Entities\Customer $instance; public function __construct(array $data = []) { - //TODO: validate data contains the required fields. - $this->data = [...$data]; + $this->instance = new \Flutterwave\Entities\Customer($data); } public function get(string $param) { - return $this->data[$param]; + return $this->instance->get($param); } public function set(string $param, $value): void { - $this->data[$param] = $value; + $this->instance->set($param, $value); } public function has(string $param): bool { - return isset($this->data[$param]); + return $this->instance->has($param); } public function toArray(): array { - return $this->data; + return $this->instance->toArray(); } } diff --git a/src/Entities/Customer.php b/src/Entities/Customer.php new file mode 100644 index 0000000..d03adce --- /dev/null +++ b/src/Entities/Customer.php @@ -0,0 +1,36 @@ +data = [...$data]; + } + + public function get(string $param) + { + return $this->data[$param]; + } + + public function set(string $param, $value): void + { + $this->data[$param] = $value; + } + + public function has(string $param): bool + { + return isset($this->data[$param]); + } + + public function toArray(): array + { + return $this->data; + } +} diff --git a/src/Entities/Payload.php b/src/Entities/Payload.php new file mode 100644 index 0000000..8dc637e --- /dev/null +++ b/src/Entities/Payload.php @@ -0,0 +1,147 @@ +has($param)) { + return null; + } + return $this->data[$param]; + } + + public function set(string $param, $value): void + { + if ($param === AuthMode::PIN) { + $this->data['otherData']['authorization']['mode'] = self::PIN; + $this->data['otherData']['authorization'][AuthMode::PIN] = $value; + } else { + $this->data[$param] = $value; + } + } + + public function delete(string $param, array $assoc_option = []): void + { + if (! isset($param)) { + return; + } + + if ($param === 'otherData' && count($assoc_option) > 0) { + foreach ($assoc_option as $option) { + unset($this->data['otherData'][$option]); + } + } + unset($this->data[$param]); + } + + public function setPayloadType(string $type): self + { + $this->type = $type; + return $this; + } + + public function toArray(?string $payment_method = null): array + { + $data = $this->data; + $customer = $data['customer'] ?? new Customer(); + $additionalData = $data['otherData'] ?? []; + + if (gettype($customer) === 'string') { + $string_value = $customer; + $customer = new Customer(); + $customer->set('customer', $string_value); + } + + switch ($payment_method) { + case 'card': + $card_details = $additionalData['card_details']; + unset($additionalData['card_details']); + $data = array_merge($data, $additionalData, $customer->toArray(), $card_details); + break; + case 'account': + $account_details = $additionalData['account_details']; + unset($additionalData['account_details']); + $data = array_merge($data, $additionalData, $customer->toArray(), $account_details); + break; + default: + $data = array_merge($data, $additionalData, $customer->toArray()); + break; + } + + if ($payment_method === 'modal') { + return $data; + } + + unset($data['customer']); + unset($data['otherData']); + + //convert customer obj to array + $data = array_merge($additionalData, $data, $customer->toArray()); + + //if $data['preauthorize'] is false unset + if (isset($data['preauthorize']) && empty($data['preauthorize'])) { + unset($data['preauthorize']); + } + + if (array_key_exists('phone_number', $data) && is_null($data['phone_number'])) { + unset($data['phone_number']); + } + + //if $data['payment_plan'] is null unset + if (isset($data['payment_plan']) && empty($data['payment_plan'])) { + unset($data['payment_plan']); + } + return $data; + } + + public function update($param, $value): void + { + if ($param === 'otherData' && \is_array($value)) { + foreach ($value as $key => $item) { + $this->data['otherData'][$key] = $item; + } + } + + $this->data = array_merge($this->data, [$param => $value]); + } + + public function empty(): void + { + $this->data = []; + } + + public function has(string $param): bool + { + if (! isset($this->data[$param])) { + return false; + } + return true; + } + + public function size(): int + { + return count($this->data); + } + + public function generateTxRef(): void + { + if ($this->has('tx_ref')) { + $this->set('tx_ref', 'FLWPHP|' . (mt_rand(2, 101) + time())); + } + } +} diff --git a/src/Enum/Bill.php b/src/Enum/Bill.php index fd5bab9..d1d389d 100644 --- a/src/Enum/Bill.php +++ b/src/Enum/Bill.php @@ -6,9 +6,9 @@ //use Cerbero\Enum\Concerns\Enumerates; -enum Bill:string +enum Bill: string { -// use Enumerates; + // use Enumerates; case AIRTIME = 'AIRTIME'; case DSTV = 'DSTV'; case DSTV_BOX_OFFICE = 'DSTV BOX OFFICE'; diff --git a/src/Enum/Currency.php b/src/Enum/Currency.php index d014213..12b32a0 100644 --- a/src/Enum/Currency.php +++ b/src/Enum/Currency.php @@ -6,9 +6,9 @@ //use Cerbero\Enum\Concerns\Enumerates; -enum Currency:string +enum Currency: string { -// use Enumerates; + // use Enumerates; case NGN = 'NGN'; case USD = 'USD'; case KES = 'KES'; diff --git a/src/Enum/Method.php b/src/Enum/Method.php index c19025d..74c5c9a 100644 --- a/src/Enum/Method.php +++ b/src/Enum/Method.php @@ -6,9 +6,9 @@ //use Cerbero\Enum\Concerns\Enumerates; -enum Method:string +enum Method: string { -// use Enumerates; + // use Enumerates; case DEFAULT = 'default'; case STANDARD = 'standard'; case CARD = 'card'; diff --git a/src/Enum/Momo.php b/src/Enum/Momo.php index adb31fe..a9c2e0c 100644 --- a/src/Enum/Momo.php +++ b/src/Enum/Momo.php @@ -6,9 +6,9 @@ //use Cerbero\Enum\Concerns\Enumerates; -enum Momo:string +enum Momo: string { -// use Enumerates; + // use Enumerates; case GHANA = 'mobile_money_ghana'; case UGANDA = 'mobile_money_uganda'; case FRANCO = 'mobile_money_franco'; diff --git a/src/EventHandlers/AccountEventHandler.php b/src/EventHandlers/AccountEventHandler.php index 335c47e..30b957c 100644 --- a/src/EventHandlers/AccountEventHandler.php +++ b/src/EventHandlers/AccountEventHandler.php @@ -4,8 +4,16 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class AccountEventHandler implements EventHandlerInterface { + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + use EventTracker; /** @@ -83,7 +91,8 @@ public function onTimeout($transactionReference, $data): void * */ public function onAuthorization(\stdClass $response, ?array $resource = null): array { - $mode = $response->meta->authorization->mode; + + $mode = $response->data->meta->authorization->mode; if (property_exists($response, 'data')) { $transactionId = $response->data->id; @@ -95,35 +104,35 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a } switch ($mode) { - case 'pin': - $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the previous request."; - $data['instruction'] = 'Enter the pin of your card'; - break; - case 'redirect': - $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; - $data['url'] = $response->meta->authorization->redirect; - break; - case 'avs': - throw new \Exception('AVS is currently not available via the SDK. please call the endpoint directly.'); - - case 'otp': - $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; - - if (property_exists($response->data, 'processor_response')) { - $data['instruction'] = $response->data->processor_response; - } else { - $data['instruction'] = $response->meta->authorization->validate_instructions; - } - - $data['validate'] = true; - break; + case 'pin': + $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the previous request."; + $data['instruction'] = 'Enter the pin of your card'; + break; + case 'redirect': + $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; + $data['url'] = $response->data->meta->authorization->redirect; + break; + case 'avs': + throw new \Exception('AVS is currently not available via the SDK. please call the endpoint directly.'); + + case 'otp': + $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; + + if (property_exists($response->data, 'processor_response')) { + $data['instruction'] = $response->data->processor_response; + } else { + $data['instruction'] = $response->data->meta->authorization->validate_instructions; + } + + $data['validate'] = true; + break; } $data['mode'] = $mode; if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Account Service::Authorization Mode: '.$mode); + $logger->notice('Account Service::Authorization Mode: ' . $mode); } return $data; diff --git a/src/EventHandlers/AchEventHandler.php b/src/EventHandlers/AchEventHandler.php index a428dc8..7dd1c11 100644 --- a/src/EventHandlers/AchEventHandler.php +++ b/src/EventHandlers/AchEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class AchEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * @@ -93,35 +101,35 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a } switch ($mode) { - case 'pin': - $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the previous request."; - $data['instruction'] = 'Enter the pin of your card'; - break; - case 'redirect': - $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; - $data['url'] = $response->meta->authorization->redirect; - break; - case 'avs': - throw new \Exception('AVS is currently not available via the SDK. please call the endpoint directly.'); - - case 'otp': - $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; - - if (property_exists($response->data, 'processor_response')) { - $data['instruction'] = $response->data->processor_response; - } else { - $data['instruction'] = $response->meta->authorization->validate_instructions; - } - - $data['validate'] = true; - break; + case 'pin': + $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the previous request."; + $data['instruction'] = 'Enter the pin of your card'; + break; + case 'redirect': + $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; + $data['url'] = $response->meta->authorization->redirect; + break; + case 'avs': + throw new \Exception('AVS is currently not available via the SDK. please call the endpoint directly.'); + + case 'otp': + $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; + + if (property_exists($response->data, 'processor_response')) { + $data['instruction'] = $response->data->processor_response; + } else { + $data['instruction'] = $response->meta->authorization->validate_instructions; + } + + $data['validate'] = true; + break; } $data['mode'] = $mode; if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Ach Event::Authorization Mode: '.$mode); + $logger->notice('Ach Event::Authorization Mode: ' . $mode); } return $data; diff --git a/src/EventHandlers/ApplePayEventHandler.php b/src/EventHandlers/ApplePayEventHandler.php index 6096a83..e984dd5 100644 --- a/src/EventHandlers/ApplePayEventHandler.php +++ b/src/EventHandlers/ApplePayEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class ApplePayEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + public function onSuccessful($transactionData): void { // TODO: Implement onSuccessful() method. @@ -55,7 +63,7 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Apple Method Event::Apple Authorization Mode: '.$data['mode'] ?? 'redirect'); + $logger->notice('Apple Method Event::Apple Authorization Mode: ' . $data['mode'] ?? 'redirect'); } return $data; diff --git a/src/EventHandlers/BankTransferEventHandler.php b/src/EventHandlers/BankTransferEventHandler.php index d69d149..b3332c8 100644 --- a/src/EventHandlers/BankTransferEventHandler.php +++ b/src/EventHandlers/BankTransferEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class BankTransferEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * @inheritDoc */ @@ -57,8 +65,8 @@ public function onTimeout($transactionReference, $data): void } /** - * @param \stdClass $response - * @param array|null $resource + * @param \stdClass $response + * @param array|null $resource * @return array */ public function onAuthorization(\stdClass $response, ?array $resource = null): array @@ -76,8 +84,8 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Transfer Authorization Mode: '.$mode); - $logger->info('Bank Transfer Event::Created Account Info :'.json_encode($data)); + $logger->notice('Transfer Authorization Mode: ' . $mode); + $logger->info('Bank Transfer Event::Created Account Info :' . json_encode($data)); } return $data; diff --git a/src/EventHandlers/BillEventHandler.php b/src/EventHandlers/BillEventHandler.php index f5fbb8e..e4752ee 100644 --- a/src/EventHandlers/BillEventHandler.php +++ b/src/EventHandlers/BillEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class BillEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/BvnEventHandler.php b/src/EventHandlers/BvnEventHandler.php index 2fe5105..34a3eb7 100644 --- a/src/EventHandlers/BvnEventHandler.php +++ b/src/EventHandlers/BvnEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class BvnEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/CardEventHandler.php b/src/EventHandlers/CardEventHandler.php index e817c07..f254321 100644 --- a/src/EventHandlers/CardEventHandler.php +++ b/src/EventHandlers/CardEventHandler.php @@ -4,12 +4,19 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; use Flutterwave\Util\AuthMode; class CardEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * @@ -100,30 +107,30 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a } switch ($mode) { - case AuthMode::PIN: - $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the payload."; - $data['instruction'] = 'Enter the pin of your card'; - break; - case AuthMode::REDIRECT: - $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; - $data['url'] = $response->meta->authorization->redirect; - break; - case AuthMode::AVS: - $data['dev_instruction'] = "Redirect user to a form to enter certain details and re-initiate the charge adding the params ['mode' => 'avs_noauth', 'city' => 'USER_CITY', 'state' => 'USER_STATE', 'country' => 'USER_COUNTRY', 'zipcode' => 'USER_ZIP'] to the payload."; - $data['instruction'] = 'please complete the form for Address Verification.'; - break; - case AuthMode::OTP: - $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; - $data['instruction'] = $response->data->processor_response; - $data['validate'] = true; - break; + case AuthMode::PIN: + $data['dev_instruction'] = "Redirect user to a form to enter his pin and re-initiate the charge adding the params ['pin' => 'USERS_PIN'] to the payload."; + $data['instruction'] = 'Enter the pin of your card'; + break; + case AuthMode::REDIRECT: + $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; + $data['url'] = $response->meta->authorization->redirect; + break; + case AuthMode::AVS: + $data['dev_instruction'] = "Redirect user to a form to enter certain details and re-initiate the charge adding the params ['mode' => 'avs_noauth', 'city' => 'USER_CITY', 'state' => 'USER_STATE', 'country' => 'USER_COUNTRY', 'zipcode' => 'USER_ZIP'] to the payload."; + $data['instruction'] = 'please complete the form for Address Verification.'; + break; + case AuthMode::OTP: + $data['dev_instruction'] = 'Redirect user to a form to validate with OTP code sent to their Phone.'; + $data['instruction'] = $response->data->processor_response; + $data['validate'] = true; + break; } $data['mode'] = $mode; if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Card Event::Authorization Mode: '.$mode); + $logger->notice('Card Event::Authorization Mode: ' . $mode); } return $data; diff --git a/src/EventHandlers/EnairaEventHandler.php b/src/EventHandlers/EnairaEventHandler.php new file mode 100644 index 0000000..6ef5b38 --- /dev/null +++ b/src/EventHandlers/EnairaEventHandler.php @@ -0,0 +1,78 @@ +data->id; + $tx_ref = $response->data->tx_ref; + $data['data_to_save'] = [ + 'transactionId' => $transactionId, + 'tx_ref' => $tx_ref, + ]; + $data['mode'] = $response->data->meta->authorization->mode; + + switch ($data['mode']) { + case AuthMode::REDIRECT: + $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; + $data['url'] = $response->meta->authorization->redirect; + break; + case AuthMode::VALIDATE: + $data['dev_instruction'] = "To complete the charge, call the validate endpoint and pass the token (OTP generated from the user's speed wallet)."; + $data['instruction'] = $response->meta->validate_instructions; + break; + } + } + + if (is_array($resource) && ! empty($resource)) { + $logger = $resource['logger']; + $logger->notice('Enaira Method Event::Enaira Authorization Mode: ' . $data['mode'] ?? 'redirect'); + } + + return $data; + } +} \ No newline at end of file diff --git a/src/EventHandlers/EventTracker.php b/src/EventHandlers/EventTracker.php index d5b8c11..0c8ba00 100644 --- a/src/EventHandlers/EventTracker.php +++ b/src/EventHandlers/EventTracker.php @@ -4,8 +4,8 @@ namespace Flutterwave\EventHandlers; -use Unirest\Request; -use Unirest\Request\Body; +use Flutterwave\Service\Service as Http; +use Psr\Http\Client\ClientExceptionInterface; trait EventTracker { @@ -22,6 +22,9 @@ public static function setResponseTime(): void self::$response_time = microtime(true) - self::$time_start; } + /** + * @throws ClientExceptionInterface + */ public static function sendAnalytics($title): void { if (self::$response_time <= 0) { @@ -37,8 +40,8 @@ public static function sendAnalytics($title): void 'title' => $title, 'message' => self::$response_time, ]; - $body = Body::json($data); - Request::post($url, [], $body); + + $response = (new Http(static::$config))->request($data, 'POST', $url, true); self::resetTime(); } diff --git a/src/EventHandlers/FawryEventHandler.php b/src/EventHandlers/FawryEventHandler.php new file mode 100644 index 0000000..a4a40d5 --- /dev/null +++ b/src/EventHandlers/FawryEventHandler.php @@ -0,0 +1,68 @@ +data->id; + $tx_ref = $response->data->tx_ref; + $data['data_to_save'] = [ + 'transactionId' => $transactionId, + 'tx_ref' => $tx_ref, + ]; + $data['mode'] = $response->data->meta->authorization->mode; + } + + $data['dev_instruction'] = 'Redirect the user to the auth link for validation. verfiy via the verify endpoint.'; + + if (is_array($resource) && ! empty($resource)) { + $logger = $resource['logger']; + $logger->notice('Fawry Method Event::Fawry Authorization Mode: ' . $data['mode'] ?? 'fawry_pay'); + } + + return $data; + } +} \ No newline at end of file diff --git a/src/EventHandlers/GooglePayEventHandler.php b/src/EventHandlers/GooglePayEventHandler.php new file mode 100644 index 0000000..d481232 --- /dev/null +++ b/src/EventHandlers/GooglePayEventHandler.php @@ -0,0 +1,71 @@ +data->id; + $tx_ref = $response->data->tx_ref; + $data['data_to_save'] = [ + 'transactionId' => $transactionId, + 'tx_ref' => $tx_ref, + ]; + $data['mode'] = $response->data->meta->authorization->mode; + } + + $data['dev_instruction'] = 'Redirect the user to the auth link for validation. verfiy via the verify endpoint.'; + $data['url'] = $response->data->meta->authorization->redirect; + + if (is_array($resource) && ! empty($resource)) { + $logger = $resource['logger']; + $logger->notice('Google Method Event::Apple Authorization Mode: ' . $data['mode'] ?? 'redirect'); + } + + return $data; + } +} \ No newline at end of file diff --git a/src/EventHandlers/ModalEventHandler.php b/src/EventHandlers/ModalEventHandler.php new file mode 100644 index 0000000..ae5fcb8 --- /dev/null +++ b/src/EventHandlers/ModalEventHandler.php @@ -0,0 +1,114 @@ +status === 'successful') { + $currency = $_SESSION['currency']; + $amount = $_SESSION['amount']; + + if ($transactionData->currency === $currency && floatval($transactionData->amount) === floatval($amount)) { + header('Location: ' . $_SESSION['success_url']); + session_destroy(); + } + + if ($transactionData->currency === $currency && floatval($transactionData->amount) < floatval($amount)) { + // TODO: replace this a custom action. + echo "This Event Handler is an Implementation of " . __NAMESPACE__ . "\EventHandlerInterface
"; + echo "Partial Payment Made ! replace this with your own action! "; + session_destroy(); + } + + if ($transactionData->currency !== $currency && floatval($transactionData->amount) === floatval($amount)) { + // TODO: replace this a custom action. + echo "This Event Handler is an Implementation of " . __NAMESPACE__ . "\EventHandlerInterface
"; + echo "Currency mismatch. please look into it ! replace this with your own action "; + session_destroy(); + } + } else { + $this->onFailure($transactionData); + } + } + + /** + * This is called only when a transaction failed + * */ + public function onFailure($transactionData): void + { + // Get the transaction from your DB using the transaction reference (txref) + // Update the db transaction record (includeing parameters that didn't exist before the transaction is completed. for audit purpose) + // You can also redirect to your failure page from here. + // TODO: replace this a custom action. + header('Location: ' . $_SESSION['failure_url']); + session_destroy(); + } + + /** + * This is called when a transaction is requeryed from the payment gateway + * */ + public function onRequery($transactionReference): void + { + // do not include any business logic here, this function is likely to be depricated. + } + + /** + * This is called a transaction requery returns with an error + * */ + public function onRequeryError($requeryResponse): void + { + echo "Flutterwave: error querying the transaction."; + // trigger webhook notification from Flutterwave. + $service = new Flutterwave\Service\Transaction(); + $service->resendFailedHooks($data->id); + header('Location: ' . $_SERVER['HTTP_ORIGIN']); + } + + /** + * This is called when a transaction is canceled by the user + * */ + public function onCancel($transactionReference): void + { + // TODO: replace this a custom action. + echo "This Event Handler is an Implementation of " . __NAMESPACE__ . "\EventHandlerInterface
"; + echo "Payment was cancelled ! replace this with your own action."; + session_destroy(); + } + + /** + * This is called when a transaction doesn't return with a success or a failure response. This can be a timedout transaction on the Rave server or an abandoned transaction by the customer. + * */ + public function onTimeout($transactionReference, $data): void + { + // trigger webhook notification from Flutterwave. + $service = new Flutterwave\Service\Transaction(); + $service->resendFailedHooks($data->id); + header('Location: ' . $_SERVER['HOST']); + } +} diff --git a/src/EventHandlers/MomoEventHandler.php b/src/EventHandlers/MomoEventHandler.php index 37991ed..1695701 100644 --- a/src/EventHandlers/MomoEventHandler.php +++ b/src/EventHandlers/MomoEventHandler.php @@ -4,12 +4,19 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; use Flutterwave\Util\AuthMode; class MomoEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * @@ -89,20 +96,21 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a $data['data_to_save'] = [ 'transactionId' => $transactionId, 'tx_ref' => $tx_ref, + 'status' => $response->data->status ]; } if (property_exists($response, 'meta')) { $mode = $response->meta->authorization->mode; switch ($mode) { - case AuthMode::REDIRECT: - $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; - $data['url'] = $response->meta->authorization->redirect; - break; - case AuthMode::CALLBACK: - $data['dev_instruction'] = "The customer needs to authorize with their mobile money service, and then we'll send you a webhook."; - $data['instruction'] = 'please kindly authorize with your mobile money service'; - break; + case AuthMode::REDIRECT: + $data['dev_instruction'] = 'Redirect the user to the auth link for validation'; + $data['url'] = $response->meta->authorization->redirect; + break; + case AuthMode::CALLBACK: + $data['dev_instruction'] = "The customer needs to authorize with their mobile money service, and then we'll send you a webhook."; + $data['instruction'] = 'please kindly authorize with your mobile money service'; + break; } } @@ -110,7 +118,7 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Momo Service::Authorization Mode: '.($mode ?? 'none')); + $logger->notice('Momo Service::Authorization Mode: ' . ($mode ?? 'none')); } return $data; diff --git a/src/EventHandlers/MpesaEventHandler.php b/src/EventHandlers/MpesaEventHandler.php index 230f504..5c0530e 100644 --- a/src/EventHandlers/MpesaEventHandler.php +++ b/src/EventHandlers/MpesaEventHandler.php @@ -4,15 +4,26 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; +use Psr\Http\Client\ClientExceptionInterface; + class MpesaEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * - * @param array - * */ + * @param array $transactionData + * + * @throws ClientExceptionInterface + */ public function onSuccessful($transactionData): void { // Get the transaction from your DB using the transaction reference (txref) diff --git a/src/EventHandlers/PaymentPlanEventHandler.php b/src/EventHandlers/PaymentPlanEventHandler.php index eed7e55..1469b9e 100644 --- a/src/EventHandlers/PaymentPlanEventHandler.php +++ b/src/EventHandlers/PaymentPlanEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class PaymentPlanEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/PayoutSubaccoutEventHandler.php b/src/EventHandlers/PayoutSubaccoutEventHandler.php index 403734d..cf96c90 100644 --- a/src/EventHandlers/PayoutSubaccoutEventHandler.php +++ b/src/EventHandlers/PayoutSubaccoutEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class PayoutSubaccoutEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + public function onSuccessful($transactionData): void { // TODO: Implement onSuccessful() method. diff --git a/src/EventHandlers/PreEventHandler.php b/src/EventHandlers/PreEventHandler.php index 75a5c81..c6e0384 100644 --- a/src/EventHandlers/PreEventHandler.php +++ b/src/EventHandlers/PreEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class PreEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + public function onSuccessful($transactionData): void { self::sendAnalytics('Initiate-Preauth'); diff --git a/src/EventHandlers/RecipientEventHandler.php b/src/EventHandlers/RecipientEventHandler.php index 9f43599..b5f7326 100644 --- a/src/EventHandlers/RecipientEventHandler.php +++ b/src/EventHandlers/RecipientEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class RecipientEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/SettlementEventHandler.php b/src/EventHandlers/SettlementEventHandler.php index 66d4620..1ad3927 100644 --- a/src/EventHandlers/SettlementEventHandler.php +++ b/src/EventHandlers/SettlementEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class SettlementEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/SubaccountEventHandler.php b/src/EventHandlers/SubaccountEventHandler.php index 33e610e..450fa4d 100644 --- a/src/EventHandlers/SubaccountEventHandler.php +++ b/src/EventHandlers/SubaccountEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class SubaccountEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/SubscriptionEventHandler.php b/src/EventHandlers/SubscriptionEventHandler.php index af103d1..06bb87b 100644 --- a/src/EventHandlers/SubscriptionEventHandler.php +++ b/src/EventHandlers/SubscriptionEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class SubscriptionEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/TkEventHandler.php b/src/EventHandlers/TkEventHandler.php index 697a69e..cba58a5 100644 --- a/src/EventHandlers/TkEventHandler.php +++ b/src/EventHandlers/TkEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class TkEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/TransactionVerificationEventHandler.php b/src/EventHandlers/TransactionVerificationEventHandler.php index 5e4cf9c..701ded8 100644 --- a/src/EventHandlers/TransactionVerificationEventHandler.php +++ b/src/EventHandlers/TransactionVerificationEventHandler.php @@ -4,14 +4,21 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class TransactionVerificationEventHandler implements EventHandlerInterface { /** * This is called only when a transaction is successful * */ - use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + public function onSuccessful($transactionData): void { // Get the transaction from your DB using the transaction reference (txref) diff --git a/src/EventHandlers/TransferEventHandler.php b/src/EventHandlers/TransferEventHandler.php index da3dee0..69cbc64 100644 --- a/src/EventHandlers/TransferEventHandler.php +++ b/src/EventHandlers/TransferEventHandler.php @@ -5,12 +5,19 @@ namespace Flutterwave\EventHandlers; use Exception; +use Flutterwave\Contract\ConfigInterface; use stdClass; class TransferEventHandler implements EventHandlerInterface { use EventTracker; + private static ?ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * @@ -83,21 +90,21 @@ public function onTimeout($transactionReference, $data): void public function onAuthorization(stdClass $response, ?array $resource = null): array { // $auth = $response->meta->authorization; -// $mode = $auth->mode; -// $data['dev_instruction'] = "Display the transfer data for the user to make a transfer to the generated account number. verify via Webhook Service."; -// $data['instruction'] = $auth->transfer_note; -// $data['transfer_reference'] = $auth->transfer_reference; -// $data['transfer_account'] = $auth->transfer_account; -// $data['transfer_bank'] = $auth->transfer_bank; -// $data['account_expiration'] = $auth->account_expiration; -// $data['transfer_amount'] = $auth->transfer_amount; -// $data['mode'] = $mode; -// -// if(is_array($resource) && !empty($resource)) -// { -// $logger = $resource['logger']; -// $logger->notice("Transfer Authorization Mode: ".$mode); -// } + // $mode = $auth->mode; + // $data['dev_instruction'] = "Display the transfer data for the user to make a transfer to the generated account number. verify via Webhook Service."; + // $data['instruction'] = $auth->transfer_note; + // $data['transfer_reference'] = $auth->transfer_reference; + // $data['transfer_account'] = $auth->transfer_account; + // $data['transfer_bank'] = $auth->transfer_bank; + // $data['account_expiration'] = $auth->account_expiration; + // $data['transfer_amount'] = $auth->transfer_amount; + // $data['mode'] = $mode; + // + // if(is_array($resource) && !empty($resource)) + // { + // $logger = $resource['logger']; + // $logger->notice("Transfer Authorization Mode: ".$mode); + // } return []; } diff --git a/src/EventHandlers/UssdEventHandler.php b/src/EventHandlers/UssdEventHandler.php index 610c8b1..fca6c09 100644 --- a/src/EventHandlers/UssdEventHandler.php +++ b/src/EventHandlers/UssdEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class UssdEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ @@ -101,7 +109,7 @@ public function onAuthorization(\stdClass $response, ?array $resource = null): a if (is_array($resource) && ! empty($resource)) { $logger = $resource['logger']; - $logger->notice('Ussd Authorization Mode: '.$data['mode']); + $logger->notice('Ussd Authorization Mode: ' . $data['mode']); } return $data; diff --git a/src/EventHandlers/VirtualAccountEventHandler.php b/src/EventHandlers/VirtualAccountEventHandler.php index eeb4269..99e7ae7 100644 --- a/src/EventHandlers/VirtualAccountEventHandler.php +++ b/src/EventHandlers/VirtualAccountEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class VirtualAccountEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * */ diff --git a/src/EventHandlers/VoucherEventHandler.php b/src/EventHandlers/VoucherEventHandler.php index 04034d6..0d6d530 100644 --- a/src/EventHandlers/VoucherEventHandler.php +++ b/src/EventHandlers/VoucherEventHandler.php @@ -4,10 +4,18 @@ namespace Flutterwave\EventHandlers; +use Flutterwave\Contract\ConfigInterface; + class VoucherEventHandler implements EventHandlerInterface { use EventTracker; + private static ConfigInterface $config; + public function __construct($config) + { + self::$config = $config; + } + /** * This is called only when a transaction is successful * diff --git a/src/Exception/ApiException.php b/src/Exception/ApiException.php index 370151d..3ead755 100644 --- a/src/Exception/ApiException.php +++ b/src/Exception/ApiException.php @@ -4,6 +4,8 @@ namespace Flutterwave\Exception; -class ApiException extends \Unirest\Exception +use Psr\Http\Client\ClientExceptionInterface; + +class ApiException extends \Exception { } diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php index 8c4f648..f6bfec8 100644 --- a/src/Exception/AuthenticationException.php +++ b/src/Exception/AuthenticationException.php @@ -6,4 +6,14 @@ class AuthenticationException extends \Exception { + public function invalidBearerToken(): void + { + $this->message = "Invalid Secret Key passed."; + } + + public function unauthorizedAccess(): void + { + $this->message = "You currently do not have permission to access this feature. + kindly reachout to the Account owner."; + } } diff --git a/src/Exception/ClientException.php b/src/Exception/ClientException.php new file mode 100644 index 0000000..f749d57 --- /dev/null +++ b/src/Exception/ClientException.php @@ -0,0 +1,12 @@ +request = $request; + } + + /** + * {@inheritdoc} + */ + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/src/Exception/RequestException.php b/src/Exception/RequestException.php new file mode 100644 index 0000000..58fa87d --- /dev/null +++ b/src/Exception/RequestException.php @@ -0,0 +1,45 @@ +request = $request; + } + + /** + * {@inheritdoc} + */ + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/src/Factories/CustomerFactory.php b/src/Factories/CustomerFactory.php new file mode 100644 index 0000000..6c264f9 --- /dev/null +++ b/src/Factories/CustomerFactory.php @@ -0,0 +1,29 @@ +set('fullname', $data['full_name']); + $person->set('email', $data['email']); + $person->set('phone_number', $data['phone']); + $person->set('address', $data['address'] ?? null); + + return $person; + } +} diff --git a/src/Factories/PayloadFactory.php b/src/Factories/PayloadFactory.php new file mode 100644 index 0000000..df4d62a --- /dev/null +++ b/src/Factories/PayloadFactory.php @@ -0,0 +1,75 @@ +validSuppliedData($data); + if (! $check['result']) { + throw new \InvalidArgumentException( + "" . $check['missing_param'] . '' . + ' is required in the payload' + ); + } + + $currency = $data['currency']; + $amount = $data['amount']; + $customer = $data['customer']; + $redirectUrl = $data['redirectUrl'] ?? null; + $otherData = $data['additionalData'] ?? null; + $phone_number = $data['phone'] ?? null; + + if (isset($data['pin']) && ! empty($data['pin'])) { + $otherData['pin'] = $data['pin']; + } + + $payload = new Load(); + + if (! \is_null($phone_number)) { + $payload->set('phone', $phone_number); + } + + $tx_ref = $data['tx_ref'] ?? $payload->generateTxRef(); + + // $payload->set('phone_number', $phone_number); // customer factory handles that + $payload->set('currency', $currency); + $payload->set('amount', $amount); + $payload->set('tx_ref', $tx_ref); + $payload->set('customer', $customer); + $payload->set('redirect_url', $redirectUrl); + $payload->set('otherData', $otherData); + + return $payload; + } + + public function validSuppliedData(array $data): array + { + $params = $this->requiredParams; + + foreach ($params as $param) { + if (! array_key_exists($param, $data)) { + return ['missing_param' => $param, 'result' => false]; + } + } + + if (! $data['customer'] instanceof \Flutterwave\Entities\Customer) { + return ['missing_param' => 'customer', 'result' => false]; + } + + return ['missing_param' => null, 'result' => true]; + } +} diff --git a/src/Flutterwave.php b/src/Flutterwave.php index b1b8849..01d1825 100644 --- a/src/Flutterwave.php +++ b/src/Flutterwave.php @@ -4,11 +4,16 @@ namespace Flutterwave; +use Flutterwave\Config\ForkConfig; use Flutterwave\EventHandlers\EventHandlerInterface; +use Flutterwave\Exception\ApiException; +use Flutterwave\Helper\CheckCompatibility; use Flutterwave\Traits\PaymentFactory; use Flutterwave\Traits\Setup\Configure; +use Flutterwave\Library\Modal; +use Psr\Http\Client\ClientExceptionInterface; -define('FLW_PHP_ASSET_DIR', __DIR__.'../assets/'); +define('FLW_PHP_ASSET_DIR', __DIR__ . '../assets/'); /** * Flutterwave PHP SDK @@ -19,25 +24,35 @@ */ class Flutterwave extends AbstractPayment { - use Configure,PaymentFactory; + use Configure; + use PaymentFactory; /** * Flutterwave Construct + * * @param string $prefix - * @param bool $overrideRefWithPrefix Set this parameter to true to use your prefix as the transaction reference + * @param bool $overrideRefWithPrefix Set this parameter to true to use your prefix as the transaction reference */ - public function __construct(string $prefix, bool $overrideRefWithPrefix = false) + public function __construct() { - parent::__construct($prefix, $overrideRefWithPrefix); - $this->overrideTransactionReference = $overrideRefWithPrefix; + parent::__construct(); + $this->checkPageIsSecure(); // create a log channel $this->logger = self::$config->getLoggerInstance(); $this->createReferenceNumber(); $this->logger->notice('Main Class Initializes....'); } + private function checkPageIsSecure() + { + if(!CheckCompatibility::isSsl() && 'production' === $this->getConfig()->getEnv()) { + throw new \Exception('Flutterwave: cannot load checkout modal on an unsecure page - no SSL detected. '); + } + } + /** * Sets the transaction amount + * * @param string $amount Transaction amount * */ public function setAmount(string $amount): object @@ -57,6 +72,16 @@ public function setPaymentOptions(string $paymentOptions): object return $this; } + /** + * get event handler. + * + * @param string $paymentOptions The allowed payment methods. Can be card, account or both + */ + public function getEventHandler() + { + return $this->handler; + } + /** * Sets the transaction description * @@ -82,7 +107,8 @@ public function setLogo(string $customLogo): object /** * Sets the payment page title * - * @param string $customTitle A title for the payment. It can be the product name, your business name or anything short and descriptive + * @param string $customTitle A title for the payment. + * It can be the product name, your business name or anything short and descriptive */ public function setTitle(string $customTitle): object { @@ -159,7 +185,8 @@ public function setPhoneNumber(string $customerPhone): object /** * Sets the payment page button text * - * @param string $payButtonText This is the text that should appear on the payment button on the Rave payment gateway. + * @param string $payButtonText This is the text that should appear + * on the payment button on the Rave payment gateway. */ public function setPayButtonText(string $payButtonText): object { @@ -170,7 +197,8 @@ public function setPayButtonText(string $payButtonText): object /** * Sets the transaction redirect url * - * @param string $redirectUrl This is where the Rave payment gateway will redirect to after completing a payment + * @param string $redirectUrl This is where the Flutterwave will redirect to after + * completing a payment */ public function setRedirectUrl(string $redirectUrl): object { @@ -181,7 +209,9 @@ public function setRedirectUrl(string $redirectUrl): object /** * Sets the transaction meta data. Can be called multiple time to set multiple meta data * - * @param array $meta This are the other information you will like to store with the transaction. It is a key => value array. eg. PNR for airlines, product colour or attributes. Example. array('name' => 'femi') + * @param array $meta This are the other information you will like to store + * with the transaction. It is a key => value array. eg. PNR for airlines, + * product colour or attributes. Example. array('name' => 'femi') */ public function setMetaData(array $meta): object { @@ -192,7 +222,8 @@ public function setMetaData(array $meta): object /** * Sets the event hooks for all available triggers * - * @param EventHandlerInterface $handler This is a class that implements the Event Handler Interface + * @param EventHandlerInterface $handler This is a class that implements the + * Event Handler Interface */ public function eventHandler(EventHandlerInterface $handler): object { @@ -203,7 +234,9 @@ public function eventHandler(EventHandlerInterface $handler): object /** * Requerys a previous transaction from the Rave payment gateway * - * @param string $referenceNumber This should be the reference number of the transaction you want to requery + * @param string $referenceNumber This should be the reference number of the transaction you want to requery + * @throws ClientExceptionInterface + * @throws ApiException */ public function requeryTransaction(string $referenceNumber): object { @@ -219,37 +252,35 @@ public function requeryTransaction(string $referenceNumber): object // 'only_successful' => '1' ]; - // make request to endpoint using unirest. - $headers = ['Content-Type' => 'application/json', 'Authorization' => 'Bearer '.self::$config->getSecretKey()]; - $body = Body::json($data); - $url = $this->baseUrl . '/transactions/' . $data['id'] . '/verify'; - // Make `POST` request and handle response with unirest - $response = Request::get($url, $headers); + $url = '/transactions/' . $data['id'] . '/verify'; -// print_r($response); + $response = $this->getURL(static::$config, $url); - //check the status is success - if ($response->body && $response->body->status === 'success') { - if ($response->body && $response->body->data && $response->body->data->status === 'successful') { - $this->logger->notice('Requeryed a successful transaction....' . json_encode($response->body->data)); - // Handle successful + //check the status is success. + if ($response->status === 'success') { + if ($response->data && $response->data->status === 'successful') { + $this->logger->notice('Requeryed a successful transaction....' . json_encode($response->data)); + // Handle successful. if (isset($this->handler)) { - $this->handler->onSuccessful($response->body->data); + $this->handler->onSuccessful($response->data); } - } elseif ($response->body && $response->body->data && $response->body->data->status === 'failed') { + } elseif ($response->data && $response->data->status === 'failed') { // Handle Failure - $this->logger->warning('Requeryed a failed transaction....' . json_encode($response->body->data)); + $this->logger->warning('Requeryed a failed transaction....' . json_encode($response->data)); if (isset($this->handler)) { - $this->handler->onFailure($response->body->data); + $this->handler->onFailure($response->data); } } else { // Handled an undecisive transaction. Probably timed out. - $this->logger->warning('Requeryed an undecisive transaction....' . json_encode($response->body->data)); - // I will requery again here. Just incase we have some devs that cannot setup a queue for requery. I don't like this. + $this->logger->warning( + 'Requeryed an undecisive transaction....' . json_encode($response->data) + ); + // I will requery again here. Just incase we have some devs that cannot setup a queue for requery. + // I don't like this. if ($this->requeryCount > 4) { // Now you have to setup a queue by force. We couldn't get a status in 5 requeries. if (isset($this->handler)) { - $this->handler->onTimeout($this->txref, $response->body); + $this->handler->onTimeout($this->txref, $response->data); } } else { $this->logger->notice('delaying next requery for 3 seconds'); @@ -259,10 +290,9 @@ public function requeryTransaction(string $referenceNumber): object } } } else { - // $this->logger->warn('Requery call returned error for transaction reference.....'.json_encode($response->body).'Transaction Reference: '. $this->txref); // Handle Requery Error if (isset($this->handler)) { - $this->handler->onRequeryError($response->body); + $this->handler->onRequeryError($response->data); } } return $this; @@ -279,10 +309,11 @@ public function initialize(): void echo ''; echo ''; -// $loader_img_src = FLW_PHP_ASSET_DIR."js/v3.js"; - echo '
Proccessing...loading-gif
'; -// $script_src = FLW_PHP_ASSET_DIR."js/v3.js"; - echo ''; + // $loader_img_src = FLW_PHP_ASSET_DIR."js/v3.js"; + echo '
+ Proccessing...loading-gif
'; + // $script_src = FLW_PHP_ASSET_DIR."js/v3.js"; + echo ''; echo ''; + $html .= ''; + $html .= ''; + $html .= ''; + + $this->logger->info('Rendered Payment Modal Successfully..'); + return $html; + } + + public function getUrl() + { + + if ($this->type !== self::STANDARD) { + return $this->returnHtml(); + } + + $default_options = CheckoutHelper::getDefaultPaymentOptions(); + $payload = $this->payload->toArray('modal'); + $currency = $payload['currency']; + $country = CheckoutHelper::getSupportedCountry($currency); + + $payload['country'] = $country; + $payload['customer'] = $payload['customer']->toArray(); + $payload['payment_method'] ?? $default_options; + + $this->logger->info('Generating Payment link for [' . $payload['tx_ref'] . ']'); + $response = (new Http(self::$config))->request($payload, 'POST', 'payments'); + return $response->data->link; + } +} diff --git a/src/Payload.php b/src/Payload.php index 01e65de..cc4644a 100644 --- a/src/Payload.php +++ b/src/Payload.php @@ -4,141 +4,72 @@ namespace Flutterwave; -use Flutterwave\Util\AuthMode; - +/** + * Class Payload + * + * @package Flutterwave + * @deprecated Use Flutterwave\Entities\Payload instead. + */ class Payload { - public const PIN = 'pin'; - public const OTP = 'otp'; - public const REDIRECT = 'redirect'; - public const NOAUTH = 'noauth'; - public const AVS = 'avs'; - - protected array $data = []; + private Entities\Payload $instance; - protected ?string $type = null; + public function __construct() + { + $this->instance = new \Flutterwave\Entities\Payload(); + } public function get(string $param) { - if (! $this->has($param)) { + if (! $this->instance->has($param)) { return null; } - return $this->data[$param]; + return $this->instance->get($param); } public function set(string $param, $value): void { - if ($param === AuthMode::PIN) { - $this->data['otherData']['authorization']['mode'] = self::PIN; - $this->data['otherData']['authorization'][AuthMode::PIN] = $value; - } else { - $this->data[$param] = $value; - } + $this->instance->set($param, $value); } public function delete(string $param, array $assoc_option = []): void { - if (! isset($param)) { - return; - } - - if ($param === 'otherData' && count($assoc_option) > 0) { - foreach ($assoc_option as $option) { - unset($this->data['otherData'][$option]); - } - } - unset($this->data[$param]); + $this->instance->delete($param, $assoc_option); } - public function setPayloadType(string $type): self + public function setPayloadType(string $type): Entities\Payload { - $this->type = $type; - return $this; + $this->instance->setPayloadType($type); + return $this->instance; } public function toArray(?string $payment_method = null): array { - $data = $this->data; - $customer = $data['customer'] ?? new Customer(); - $additionalData = $data['otherData'] ?? []; - - if (gettype($customer) === 'string') { - $string_value = $customer; - $customer = new Customer(); - $customer->set('customer', $string_value); - } - - switch ($payment_method) { - case 'card': - $card_details = $additionalData['card_details']; - unset($additionalData['card_details']); - $data = array_merge($data, $additionalData, $customer->toArray(), $card_details); - break; - case 'account': - $account_details = $additionalData['account_details']; - unset($additionalData['account_details']); - $data = array_merge($data, $additionalData, $customer->toArray(), $account_details); - break; - default: - $data = array_merge($data, $additionalData, $customer->toArray()); - break; - } - - unset($data['customer']); - unset($data['otherData']); - - //convert customer obj to array - $data = array_merge($additionalData, $data, $customer->toArray()); - - //if $data['preauthorize'] is false unset - if (isset($data['preauthorize']) && empty($data['preauthorize'])) { - unset($data['preauthorize']); - } - - if (array_key_exists('phone_number', $data) && is_null($data['phone_number'])) { - unset($data['phone_number']); - } - - //if $data['payment_plan'] is null unset - if (isset($data['payment_plan']) && empty($data['payment_plan'])) { - unset($data['payment_plan']); - } - return $data; + return $this->instance->toArray($payment_method); } public function update($param, $value): void { - if ($param === 'otherData' && \is_array($value)) { - foreach ($value as $key => $item) { - $this->data['otherData'][$key] = $item; - } - } - - $this->data = array_merge($this->data, [$param => $value]); + $this->instance->update($param, $value); } public function empty(): void { - $this->data = []; + $this->instance->empty(); } public function has(string $param): bool { - if (! isset($this->data[$param])) { - return false; - } - return true; + return $this->instance->has($param); } public function size(): int { - return count($this->data); + return $this->instance->size(); } public function generateTxRef(): void { - if ($this->has('tx_ref')) { - $this->set('tx_ref', 'FLWPHP|' . (mt_rand(2, 101) + time())); - } + $this->instance->generateTxRef(); } } diff --git a/src/Service/AccountPayment.php b/src/Service/AccountPayment.php index ef7c605..1748883 100644 --- a/src/Service/AccountPayment.php +++ b/src/Service/AccountPayment.php @@ -8,23 +8,27 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\AccountEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; +use Flutterwave\Util\Currency; use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; +use Psr\Http\Client\ClientExceptionInterface; use stdClass; class AccountPayment extends Service implements Payment { use Charge; + public const ENDPOINT = 'charge'; - public const DEBIT_NG = 'debit_ng_account'; - public const DEBIT_UK = 'debit_uk_account'; + public const DEBIT_NG = 'mono'; + public const DEBIT_UK = 'account-ach-uk'; public const TYPE = 'account'; protected array $accounts = [ - 'NG' => self::DEBIT_NG, - 'UK' => self::DEBIT_UK, - ]; + Currency::NGN => self::DEBIT_NG, + Currency::GBP => self::DEBIT_UK, + Currency::EUR => self::DEBIT_UK + ]; protected string $country = 'NG'; private AccountEventHandler $eventHandler; @@ -34,8 +38,8 @@ public function __construct(?ConfigInterface $config = null) $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new AccountEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new AccountEventHandler($config); } public function setCountry(string $country): void @@ -53,6 +57,12 @@ public function setCountry(string $country): void */ public function initiate(Payload $payload): array { + if($payload->has('currency') && !key_exists($payload->get('currency'), $this->accounts)) { + $msg = 'Account Service: The Currency passed is not supported. kindy pass NGN, GBP or EUR.'; + $this->logger->info($msg); + throw new InvalidArgumentException($msg); + } + if ($this->checkPayloadIsValid($payload, 'account_details')) { return $this->charge($payload); } @@ -62,23 +72,26 @@ public function initiate(Payload $payload): array } /** + * @param Payload $payload * @return array * - * @throws GuzzleException - * @throws Exception + * @throws ClientExceptionInterface */ public function charge(Payload $payload): array { $this->logger->notice('Account Service::Charging Account ...'); - $this->checkSpecialCasesParams($payload); + if($payload->has('currency') && $payload->get('currency') === Currency::NGN ) { + $this->checkSpecialCasesParams($payload); + } + $payload = $payload->toArray(self::TYPE); //request payload $body = $payload; //check which country was passed. - $account = $this->accounts[$payload['country']]; + $account = $this->accounts[$payload['currency']]; unset($body['country']); unset($body['address']); @@ -97,7 +110,7 @@ public function save(callable $callback): void private function checkSpecialCasesParams(Payload $payload) { $details = $payload->get('otherData')['account_details']; - $banks = require __DIR__ . '/../Util/unique_bank_cases.php'; + $banks = include __DIR__ . '/../Util/unique_bank_cases.php'; foreach ($banks as $code => $case) { if ($details['account_bank'] === $code) { @@ -125,7 +138,8 @@ private function checkSpecialCasesParams(Payload $payload) } /** - * @param array $payload + * @param stdClass $response + * @param array $payload * * @return array * diff --git a/src/Service/AchPayment.php b/src/Service/AchPayment.php index 75e4816..653b0e2 100644 --- a/src/Service/AchPayment.php +++ b/src/Service/AchPayment.php @@ -8,8 +8,9 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\AchEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; +use Flutterwave\Util\Currency; use GuzzleHttp\Exception\GuzzleException; use stdClass; @@ -18,12 +19,11 @@ class AchPayment extends Service implements Payment use Charge; public const TYPE = 'ach_payment'; - public const USD = 'USD'; - public const ZAR = 'ZAR'; + protected string $country = 'US'; protected array $currency = [ - self::USD => 'US', - self::ZAR => 'ZA', + Currency::USD => 'US', + Currency::ZAR => 'ZA', ]; private AchEventHandler $eventHandler; @@ -32,8 +32,8 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new AchEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new AchEventHandler($config); } public function setCountry(string $country): void diff --git a/src/Service/ApplePay.php b/src/Service/ApplePay.php index 5ab7812..498f7f5 100644 --- a/src/Service/ApplePay.php +++ b/src/Service/ApplePay.php @@ -7,9 +7,10 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\ApplePayEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Client\ClientExceptionInterface; use stdClass; class ApplePay extends Service implements Payment @@ -24,8 +25,8 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new ApplePayEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new ApplePayEventHandler($config); } /** @@ -39,9 +40,10 @@ public function initiate(Payload $payload): array } /** + * @param Payload $payload * @return array * - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function charge(Payload $payload): array { diff --git a/src/Service/BankTransfer.php b/src/Service/BankTransfer.php index b150213..748038d 100644 --- a/src/Service/BankTransfer.php +++ b/src/Service/BankTransfer.php @@ -8,9 +8,10 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\BankTransferEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Client\ClientExceptionInterface; use stdClass; class BankTransfer extends Service implements Payment @@ -26,8 +27,8 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new BankTransferEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new BankTransferEventHandler($config); } public function makePermanent(): void @@ -38,10 +39,10 @@ public function makePermanent(): void } /** - * @param Payload $payload + * @param Payload $payload * @return array * - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function initiate(Payload $payload): array { @@ -49,11 +50,11 @@ public function initiate(Payload $payload): array } /** - * @param Payload $payload + * @param Payload $payload * @return array * - * @throws GuzzleException - * @throws Exception + * @throws ClientExceptionInterface + * @throws Exception|ClientExceptionInterface */ public function charge(Payload $payload): array { @@ -65,9 +66,9 @@ public function charge(Payload $payload): array //request payload $body = $payload; - BankTransferEventHandler::startRecording(); + $this->eventHandler::startRecording(); $request = $this->request($body, 'POST', self::TYPE); - BankTransferEventHandler::setResponseTime(); + $this->eventHandler::setResponseTime(); return $this->handleAuthState($request, $body); } @@ -77,8 +78,8 @@ public function save(callable $callback): void } /** - * @param stdClass $response - * @param array $payload + * @param stdClass $response + * @param array $payload * @return array * @throws Exception */ diff --git a/src/Service/Banks.php b/src/Service/Banks.php index 132f180..17795d7 100644 --- a/src/Service/Banks.php +++ b/src/Service/Banks.php @@ -6,11 +6,13 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Client\ClientExceptionInterface; +use stdClass; class Banks extends Service { use EventTracker; + private string $name = 'banks'; public function __construct(?ConfigInterface $config = null) { @@ -18,25 +20,25 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws GuzzleException + * @throws ClientExceptionInterface */ - public function getByCountry(string $country = 'NG'): \stdClass + public function getByCountry(string $country = 'NG'): stdClass { $this->logger->notice("Bank Service::Retrieving banks in country:({$country})."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$country}"); + $response = $this->request(null, 'GET', $this->name . "/{$country}"); self::setResponseTime(); return $response; } /** - * @throws GuzzleException + * @throws ClientExceptionInterface */ - public function getBranches(string $id): \stdClass + public function getBranches(string $id): stdClass { $this->logger->notice("Bank Service::Retrieving Bank Branches bank_id:({$id})."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}/branches"); + $response = $this->request(null, 'GET', $this->name . "/{$id}/branches"); self::setResponseTime(); return $response; } diff --git a/src/Service/Beneficiaries.php b/src/Service/Beneficiaries.php index d4c5f04..de10433 100644 --- a/src/Service/Beneficiaries.php +++ b/src/Service/Beneficiaries.php @@ -6,14 +6,16 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; +use Psr\Http\Client\ClientExceptionInterface; use stdClass; class Beneficiaries extends Service { use EventTracker; + private string $name = 'beneficiaries'; private array $requiredParams = [ 'account_bank','account_number','beneficiary_name', @@ -24,21 +26,25 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws GuzzleException + * @param Payload $payload + * @return stdClass + * @throws ClientExceptionInterface */ public function create(Payload $payload): stdClass { $payload = $payload->toArray(); if (array_key_exists('customer', $payload)) { - $this->logger->error('Beneficiaries Service::The required parameter customer Object is not present in payload'); - throw new InvalidArgumentException('Beneficiaries Service:The required parameter Object is not present in payload'); + $msg = 'The required parameter customer Object is not present in payload'; + $this->logger->error('Beneficiaries Service::' . $msg); + throw new InvalidArgumentException('Beneficiaries Service:' . $msg); } foreach ($this->requiredParams as $param) { if (! array_key_exists($param, $payload)) { - $this->logger->error("Beneficiaries Service::The required parameter {$param} is not present in payload"); - throw new InvalidArgumentException("Beneficiaries Service:The required parameter {$param} is not present in payload"); + $msg = 'The required parameter {$param} is not present in payload'; + $this->logger->error("Beneficiaries Service::$msg"); + throw new InvalidArgumentException("Beneficiaries Service:$msg"); } } @@ -53,7 +59,8 @@ public function create(Payload $payload): stdClass } /** - * @throws GuzzleException + * @return stdClass + * @throws ClientExceptionInterface */ public function list(): stdClass { @@ -65,25 +72,29 @@ public function list(): stdClass } /** - * @throws GuzzleException + * @param string $id + * @return stdClass + * @throws ClientExceptionInterface */ public function get(string $id): stdClass { $this->logger->notice('Beneficiaries Service::Retrieving a Beneficiary.'); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}"); + $response = $this->request(null, 'GET', $this->name . "/{$id}"); self::setResponseTime(); return $response; } /** - * @throws GuzzleException + * @param string $id + * @return stdClass + * @throws ClientExceptionInterface */ public function delete(string $id): stdClass { $this->logger->notice('Beneficiaries Service::Delete a Beneficiary.'); self::startRecording(); - $response = $this->request(null, 'DELETE', $this->name."/{$id}"); + $response = $this->request(null, 'DELETE', $this->name . "/{$id}"); self::setResponseTime(); return $response; } diff --git a/src/Service/Bill.php b/src/Service/Bill.php index dcc177e..661cce5 100644 --- a/src/Service/Bill.php +++ b/src/Service/Bill.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Bill extends Service { use EventTracker; + protected ?array $categories = null; private string $name = 'bill-categories'; private array $requiredParams = [ @@ -19,11 +20,11 @@ class Bill extends Service public function __construct(?ConfigInterface $config = null) { parent::__construct($config); - $this->categories = require __DIR__ . '/../Util/bill_categories.php'; + $this->categories = include __DIR__ . '/../Util/bill_categories.php'; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getCategories(): \stdClass { @@ -35,28 +36,28 @@ public function getCategories(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function validateService(string $item_code): \stdClass { $this->logger->notice('Bill Payment Service::Retrieving all Plans.'); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."bill-item/{$item_code}/validate"); + $response = $this->request(null, 'GET', $this->name . "bill-item/{$item_code}/validate"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function createPayment(\Flutterwave\Payload $payload): \stdClass { - $payload = $payload = $payload->toArray(); foreach ($this->requiredParams as $param) { if (! array_key_exists($param, $payload)) { - $this->logger->error("Bill Payment Service::The required parameter {$param} is not present in payload"); - throw new \InvalidArgumentException("Bill Payment Service:The required parameter {$param} is not present in payload"); + $msg = 'The required parameter {$param} is not present in payload'; + $this->logger->error("Bill Payment Service::$msg"); + throw new \InvalidArgumentException("Bill Payment Service:$msg"); } } @@ -73,8 +74,9 @@ public function createPayment(\Flutterwave\Payload $payload): \stdClass public function createBulkPayment(array $bulkPayload): \stdClass { if (empty($bulkPayload)) { - $this->logger->error('Bill Payment Service::Bulk Payload is empty. Pass a filled array'); - throw new \InvalidArgumentException('Bill Payment Service::Bulk Payload is currently empty. Pass a filled array'); + $msg = 'Bulk Payload is empty. Pass a filled array'; + $this->logger->error('Bill Payment Service::' . $msg); + throw new \InvalidArgumentException('Bill Payment Service::' . $msg); } $body = $bulkPayload; @@ -87,7 +89,7 @@ public function createBulkPayment(array $bulkPayload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getBillStatus(string $reference): \stdClass { @@ -99,7 +101,7 @@ public function getBillStatus(string $reference): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getBillPayments(): \stdClass { diff --git a/src/Service/CardPayment.php b/src/Service/CardPayment.php index f6eb668..0bbf487 100644 --- a/src/Service/CardPayment.php +++ b/src/Service/CardPayment.php @@ -4,17 +4,20 @@ namespace Flutterwave\Service; +use Exception; use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\CardEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; +use Psr\Http\Client\ClientExceptionInterface; class CardPayment extends Service implements Payment { use Charge; + public const ENDPOINT = 'charges'; public const TYPE = 'card'; protected static int $count = 0; @@ -31,17 +34,17 @@ public function __construct(?ConfigInterface $config = null) $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='.self::TYPE; - $this->end_point = self::ENDPOINT.'?type='.self::TYPE; - $this->eventHandler = new CardEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type=' . self::TYPE; + $this->end_point = self::ENDPOINT . '?type=' . self::TYPE; + $this->eventHandler = new CardEventHandler($config); } /** - * @param Payload $payload + * @param Payload $payload * @return array * @throws GuzzleException */ - public function initiate(\Flutterwave\Payload $payload): array + public function initiate(Payload $payload): array { if (self::$count >= 2) { //TODO: if payload does not have pin on 2nd request, trigger a warning. @@ -64,11 +67,12 @@ public function save(callable $callback): void } /** + * @param Payload $payload * @return array * - * @throws GuzzleException + * @throws ClientExceptionInterface */ - public function charge(\Flutterwave\Payload $payload): array + public function charge(Payload $payload): array { $tx_ref = $payload->get('tx_ref'); $this->logger->notice("Card Service::Started Charging Card tx_ref:({$tx_ref})..."); @@ -86,16 +90,18 @@ public function charge(\Flutterwave\Payload $payload): array 'client' => $client, ]; - CardEventHandler::startRecording(); + $this->eventHandler::startRecording(); $request = $this->request($body, 'POST'); - CardEventHandler::setResponseTime(); + $this->eventHandler::setResponseTime(); return $this->handleAuthState($request, $payload); } /** - * this is the encrypt3Des function that generates an encryption Key for you by passing your transaction Util and Secret Key as a parameter. - * @param string $data - * @param $key + * this is the encrypt3Des function that generates an encryption Key for you + * by passing your transaction Util and Secret Key as a parameter. + * + * @param string $data + * @param $key * @return string */ @@ -118,7 +124,10 @@ public function encryption(string $params): string } /** - * @throws \Exception + * @param \stdClass $response + * @param $payload + * @return array + * @throws Exception */ public function handleAuthState(\stdClass $response, $payload): array { diff --git a/src/Service/ChargeBacks.php b/src/Service/ChargeBacks.php index d5082fd..f61812d 100644 --- a/src/Service/ChargeBacks.php +++ b/src/Service/ChargeBacks.php @@ -11,6 +11,7 @@ class ChargeBacks extends Service { use EventTracker; + private string $name = 'chargebacks'; public function __construct(?ConfigInterface $config = null) { @@ -24,7 +25,7 @@ public function get(string $flw_ref): \stdClass { $this->logger->notice("ChargeBacks Service::Retrieving Chargeback.[flw_ref:{$flw_ref}]"); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."?flw_ref={$flw_ref}"); + $response = $this->request(null, 'GET', $this->name . "?flw_ref={$flw_ref}"); self::setResponseTime(); return $response; } @@ -37,7 +38,7 @@ public function getAll(array $filters = []): \stdClass $query = http_build_query($filters) ?? ''; $this->logger->notice('ChargeBacks Service::Retrieving Chargebacks.[all]'); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."?{$query}"); + $response = $this->request(null, 'GET', $this->name . "?{$query}"); self::setResponseTime(); return $response; } @@ -49,7 +50,7 @@ public function accept(string $chargeback_id): \stdClass { $this->logger->notice("ChargeBacks Service::Accepting Chargeback [{$chargeback_id}]."); self::startRecording(); - $response = $this->request([ 'action' => 'accept'], 'PUT', $this->name."/{$chargeback_id}"); + $response = $this->request([ 'action' => 'accept'], 'PUT', $this->name . "/{$chargeback_id}"); self::setResponseTime(); return $response; } @@ -61,7 +62,7 @@ public function decline(string $chargeback_id): \stdClass { $this->logger->notice("ChargeBacks Service::Declining Chargeback [{$chargeback_id}]."); self::startRecording(); - $response = $this->request([ 'action' => 'decline'], 'PUT', $this->name."/{$chargeback_id}"); + $response = $this->request([ 'action' => 'decline'], 'PUT', $this->name . "/{$chargeback_id}"); self::setResponseTime(); return $response; } diff --git a/src/Service/CollectionSubaccount.php b/src/Service/CollectionSubaccount.php index 9fd56d4..b28ac75 100644 --- a/src/Service/CollectionSubaccount.php +++ b/src/Service/CollectionSubaccount.php @@ -6,20 +6,27 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\SubaccountEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use GuzzleHttp\Exception\GuzzleException; class CollectionSubaccount extends Service { private SubaccountEventHandler $eventHandler; + private string $name = 'subaccounts'; - private array $requiredParams = [ 'account_bank', 'account_number', 'business_name', 'split_value', 'business_mobile','business_email', 'country' ]; + + private array $requiredParams = [ + 'account_bank', 'account_number', + 'business_name', 'split_value', + 'business_mobile','business_email', 'country' + ]; private array $requiredParamsUpdate = [ 'split_value']; + public function __construct(?ConfigInterface $config = null) { parent::__construct($config); $endpoint = $this->name; - $this->url = $this->baseUrl.'/'.$endpoint; + $this->url = $this->baseUrl . '/' . $endpoint; $this->eventHandler = new SubaccountEventHandler(); } @@ -27,8 +34,9 @@ public function confirmPayload(Payload $payload): array { foreach ($this->requiredParams as $param) { if (! $payload->has($param)) { - $this->logger->error("Subaccount Service::The required parameter {$param} is not present in payload"); - throw new \InvalidArgumentException("Subaccount Service:The required parameter {$param} is not present in payload"); + $msg = "The required parameter {$param} is not present in payload"; + $this->logger->error("Subaccount Service::" . $msg); + throw new \InvalidArgumentException("Subaccount Service:" . $msg); } } @@ -36,7 +44,7 @@ public function confirmPayload(Payload $payload): array } /** - * @param Payload $payload + * @param Payload $payload * @return \stdClass * @throws GuzzleException */ @@ -71,7 +79,7 @@ public function list(): \stdClass } /** - * @param string $id + * @param string $id * @return \stdClass * @throws GuzzleException */ @@ -84,8 +92,8 @@ public function get(string $id): \stdClass } /** - * @param string $id - * @param Payload $payload + * @param string $id + * @param Payload $payload * @return \stdClass * @throws GuzzleException */ @@ -93,8 +101,9 @@ public function update(string $id, Payload $payload): \stdClass { foreach ($this->requiredParamsUpdate as $param) { if (! $payload->has($param)) { - $this->logger->error("Subaccount Service::The required parameter {$param} is not present in payload"); - throw new \InvalidArgumentException("Subaccount Service:The required parameter {$param} is not present in payload"); + $msg = "The required parameter {$param} is not present in payload"; + $this->logger->error("Subaccount Service::" . $msg); + throw new \InvalidArgumentException("Subaccount Service:" . $msg); } } @@ -106,7 +115,7 @@ public function update(string $id, Payload $payload): \stdClass } /** - * @param string $id + * @param string $id * @return \stdClass * @throws GuzzleException */ diff --git a/src/Service/Customer.php b/src/Service/Customer.php index bfaf444..f924b6a 100644 --- a/src/Service/Customer.php +++ b/src/Service/Customer.php @@ -5,24 +5,26 @@ namespace Flutterwave\Service; use Flutterwave\Contract\CustomerInterface; -use Flutterwave\Customer as Person; +use Flutterwave\Entities\Customer as Person; +use Flutterwave\Factories\CustomerFactory; use InvalidArgumentException; -class Customer implements CustomerInterface +/** + * Class Customer. + * + * @deprecated use \Flutterwave\Factories\CustomerFactory instead + */ +class Customer { - public function create(array $data = []): Person - { - $data = array_change_key_case($data); - if (empty($data)) { - throw new InvalidArgumentException('Customer data is empty'); - } + protected CustomerInterface $customerFactory; - $person = new Person(); - $person->set('fullname', $data['full_name']); - $person->set('email', $data['email']); - $person->set('phone_number', $data['phone']); - $person->set('address', $data['address'] ?? null); + public function __construct() + { + $this->customerFactory = new CustomerFactory(); + } - return $person; + public function create(array $data = []): Person + { + return $this->customerFactory->create($data); } } diff --git a/src/Service/Enaira.php b/src/Service/Enaira.php new file mode 100644 index 0000000..3b928ad --- /dev/null +++ b/src/Service/Enaira.php @@ -0,0 +1,75 @@ +getEndpoint(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new ApplePayEventHandler($config); + } + + /** + * @return array + * + * @throws GuzzleException + */ + public function initiate(Payload $payload): array + { + return $this->charge($payload); + } + + /** + * @param Payload $payload + * @return array + * + * @throws ClientExceptionInterface + */ + public function charge(Payload $payload): array + { + $this->logger->notice('Enaira Service::Started Charging Process ...'); + + $payload = $payload->toArray(); + + //request payload + $body = $payload; + + ApplePayEventHandler::startRecording(); + $request = $this->request($body, 'POST', self::TYPE); + ApplePayEventHandler::setResponseTime(); + return $this->handleAuthState($request, $body); + } + + public function save(callable $callback): void + { + // TODO: Implement save() method. + } + + /** + * @param array $payload + * + * @return array + */ + private function handleAuthState(stdClass $response, array $payload): array + { + return $this->eventHandler->onAuthorization($response, ['logger' => $this->logger]); + } +} \ No newline at end of file diff --git a/src/Service/Fawry.php b/src/Service/Fawry.php new file mode 100644 index 0000000..0eaad3f --- /dev/null +++ b/src/Service/Fawry.php @@ -0,0 +1,81 @@ +getEndpoint(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new GooglePayEventHandler($config); + } + + /** + * @return array + * + * @throws GuzzleException + */ + public function initiate(Payload $payload): array + { + return $this->charge($payload); + } + + /** + * @param Payload $payload + * @return array + * + * @throws ClientExceptionInterface + */ + public function charge(Payload $payload): array + { + $this->logger->notice('Google Service::Started Charging Process ...'); + + $payload = $payload->toArray(); + + if($payload['currency'] !== 'EGP') { + throw new \InvalidArgumentException("Invalid currency. This transaction is only allowed for EGP"); + } + + //request payload + $body = $payload; + + unset($body['country']); + unset($body['address']); + + $this->eventHandler::startRecording(); + $request = $this->request($body, 'POST', self::TYPE); + $this->eventHandler::setResponseTime(); + return $this->handleAuthState($request, $body); + } + + public function save(callable $callback): void + { + // TODO: Implement save() method. + } + + /** + * @param array $payload + * + * @return array + */ + private function handleAuthState(stdClass $response, array $payload): array + { + return $this->eventHandler->onAuthorization($response, ['logger' => $this->logger]); + } +} \ No newline at end of file diff --git a/src/Service/GooglePay.php b/src/Service/GooglePay.php new file mode 100644 index 0000000..2868f5c --- /dev/null +++ b/src/Service/GooglePay.php @@ -0,0 +1,79 @@ +getEndpoint(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new GooglePayEventHandler($config); + } + + /** + * @return array + * + * @throws GuzzleException + */ + public function initiate(Payload $payload): array + { + return $this->charge($payload); + } + + /** + * @param Payload $payload + * @return array + * + * @throws ClientExceptionInterface + */ + public function charge(Payload $payload): array + { + $this->logger->notice('Google Service::Started Charging Process ...'); + + $payload = $payload->toArray(); + + //request payload + $body = $payload; + + unset($body['country']); + unset($body['address']); + + $this->eventHandler::startRecording(); + $request = $this->request($body, 'POST', self::TYPE); + $this->eventHandler::setResponseTime(); + return $this->handleAuthState($request, $body); + } + + public function save(callable $callback): void + { + // TODO: Implement save() method. + } + + /** + * @param array $payload + * + * @return array + */ + private function handleAuthState(stdClass $response, array $payload): array + { + return $this->eventHandler->onAuthorization($response, ['logger' => $this->logger]); + } +} \ No newline at end of file diff --git a/src/Service/Misc.php b/src/Service/Misc.php index 59cf6b7..ac4f9fc 100644 --- a/src/Service/Misc.php +++ b/src/Service/Misc.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Misc extends Service { use EventTracker; + private string $name = 'balances'; private array $requiredParamsHistory = [ 'from','to','currency', @@ -27,7 +28,7 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getWallet($currency): \stdClass { @@ -39,7 +40,7 @@ public function getWallet($currency): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getWallets(): \stdClass { @@ -51,14 +52,15 @@ public function getWallets(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getBalanceHistory(array $queryParams): \stdClass { foreach ($this->requiredParamsHistory as $param) { if (! array_key_exists($param, $queryParams)) { - $this->logger->error("Misc Service::The following parameter is missing to check balance history: {$param}"); - throw new \InvalidArgumentException("The following parameter is missing to check balance history: {$param}"); + $msg = "The following parameter is missing to check balance history: {$param}"; + $this->logger->error("Misc Service::$msg"); + throw new \InvalidArgumentException($msg); } } @@ -71,7 +73,7 @@ public function getBalanceHistory(array $queryParams): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function resolveAccount(\Flutterwave\Payload $payload): \stdClass { @@ -91,7 +93,7 @@ public function resolveAccount(\Flutterwave\Payload $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function resolveBvn(string $bvn): \stdClass { @@ -103,7 +105,7 @@ public function resolveBvn(string $bvn): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function resolveCardBin(string $bin): \stdClass { @@ -115,14 +117,15 @@ public function resolveCardBin(string $bin): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function userBackgroundCheck(array $data): \stdClass { foreach ($this->requiredParamsUserBackground as $param) { if (! array_key_exists($param, $data)) { - $this->logger->error("Misc Service::The following parameter is missing to check user background: {$param}"); - throw new \InvalidArgumentException("The following parameter is missing to check user background: {$param}"); + $msg = "The following parameter is missing to check user background: {$param}"; + $this->logger->error("Misc Service::$msg"); + throw new \InvalidArgumentException($msg); } } diff --git a/src/Service/MobileMoney.php b/src/Service/MobileMoney.php index 9a2369f..a546bc8 100644 --- a/src/Service/MobileMoney.php +++ b/src/Service/MobileMoney.php @@ -7,10 +7,10 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\MomoEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use Flutterwave\Util\Currency; -use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Client\ClientExceptionInterface; class MobileMoney extends Service implements Payment { @@ -23,12 +23,14 @@ class MobileMoney extends Service implements Payment Currency::UGX => 'mobile_money_uganda', Currency::XAF => 'mobile_money_franco', Currency::ZMW => 'mobile_money_zambia', + Currency::TZS => 'mobile_money_tanzania' ]; private array $networks = [ 'GH' => ['MTN','VODOFONE','TIGO'], 'UG' => ['MTN', 'AIRTEL'], 'ZM' => ['MTN', 'ZAMTEL'], + 'Tz' => ['AIRTEL', 'TIGO', 'HALOPESA', 'VODOFONE' ] ]; private array $supported_countries_franco = [ @@ -40,39 +42,42 @@ public function __construct(?ConfigInterface $config = null) { parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new MomoEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new MomoEventHandler($config); } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws \Exception + * @throws ClientExceptionInterface */ - public function initiate(\Flutterwave\Payload $payload): array + public function initiate(Payload $payload): array { return $this->charge($payload); } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws GuzzleException + * @throws ClientExceptionInterface */ - public function charge(\Flutterwave\Payload $payload): array + public function charge(Payload $payload): array { $currency = $payload->get('currency'); $otherData = $payload->get('otherData'); if (! array_key_exists($currency, $this->types)) { $supported_currencies = json_encode(array_keys($this->types)); - $this->logger->warning("Momo Service::The currency {$currency} is not supported for this payment method. options [ {$supported_currencies} ]"); - throw new \InvalidArgumentException("The currency {$currency} is not supported for this payment method. options [ {$supported_currencies} ]"); + $msg = "The currency {$currency} is not supported for this payment method. + options [ {$supported_currencies} ]"; + $this->logger->warning("Momo Service:: $msg"); + throw new \InvalidArgumentException($msg); } if (is_null($otherData)) { - $this->logger->error("Momo Service::Please pass the parameter 'network' into the additionalData array"); - throw new \InvalidArgumentException("Please pass the parameter 'network' into the additionalData array"); + $msg = "Please pass the parameter 'network' into the additionalData array"; + $this->logger->error("Momo Service::$msg"); + throw new \InvalidArgumentException($msg); } $this->isNetworkValid($otherData, $currency); @@ -86,7 +91,6 @@ public function charge(\Flutterwave\Payload $payload): array MomoEventHandler::startRecording(); $request = $this->request($body, 'POST', $type); MomoEventHandler::setResponseTime(); - return $this->handleAuthState($request, $body); } @@ -97,55 +101,60 @@ public function save(callable $callback): void private function isNetworkValid(array $otherData, string $currency): bool { - switch($currency) { - case Currency::GHS: - if (! isset($otherData['network'])) { - $this->logger->error('Ghana Momo Service::network parameter is required.'); - throw new \InvalidArgumentException('Ghana Momo Service: network parameter is required.'); - } - if (! in_array($otherData['network'], $this->networks['GH'])) { - $this->logger->error('network passed is not supported for ghana momo.'); - throw new \InvalidArgumentException('Ghana Momo Service: network passed is not supported. options: '. json_encode($this->networks['GH'])); - } - break; - case Currency::UGX: - if (! isset($otherData['network'])) { - $this->logger->error('Uganda Momo Service::network parameter is required.'); - throw new \InvalidArgumentException('Uganda Momo Service: network parameter is required.'); - } - if (! in_array($otherData['network'], $this->networks['UG'])) { - $this->logger->error('network passed is not supported for uganda momo.'); - throw new \InvalidArgumentException('Uganda Momo Service: network passed is not supported.'); - } - break; - case Currency::ZMW: - if (! isset($otherData['network'])) { - $this->logger->error('Zambia Momo Service::network parameter is required.'); - throw new \InvalidArgumentException('Uganda Momo Service: network parameter is required.'); - } - if (! in_array($otherData['network'], $this->networks['ZM'])) { - $this->logger->error('network passed is not supported for zambia momo.'); - throw new \InvalidArgumentException('Zambia Momo Service: network passed is not supported.'); - } - break; - case Currency::XAF: - if (! isset($otherData['country'])) { - $this->logger->error('Franco Momo Service::country parameter is required.'); - throw new \InvalidArgumentException('Franco Momo Service: country parameter is required.'); - } - if (! in_array($otherData['country'], $this->supported_countries_franco)) { - $this->logger->error('Franco Momo Service::country passed is not supported.'); - throw new \InvalidArgumentException('Franco Momo Service: country passed is not supported.'); - } - break; + switch ($currency) { + case Currency::GHS: + if (! isset($otherData['network'])) { + $msg = "network parameter is required."; + $this->logger->error('Ghana Momo Service::' . $msg); + throw new \InvalidArgumentException('Ghana Momo Service:' . $msg); + } + if (! in_array($otherData['network'], $this->networks['GH'])) { + $msg = "network passed is not supported."; + $this->logger->error('Ghana Momo Service::' . $msg); + throw new \InvalidArgumentException( + 'Ghana Momo Service: ' . $msg . + '. options: ' . json_encode($this->networks['GH']) + ); + } + break; + case Currency::UGX: + if (! isset($otherData['network'])) { + $this->logger->error('Uganda Momo Service::network parameter is required.'); + throw new \InvalidArgumentException('Uganda Momo Service: network parameter is required.'); + } + if (! in_array($otherData['network'], $this->networks['UG'])) { + $this->logger->error('network passed is not supported for uganda momo.'); + throw new \InvalidArgumentException('Uganda Momo Service: network passed is not supported.'); + } + break; + case Currency::ZMW: + if (! isset($otherData['network'])) { + $this->logger->error('Zambia Momo Service::network parameter is required.'); + throw new \InvalidArgumentException('Uganda Momo Service: network parameter is required.'); + } + if (! in_array($otherData['network'], $this->networks['ZM'])) { + $this->logger->error('network passed is not supported for zambia momo.'); + throw new \InvalidArgumentException('Zambia Momo Service: network passed is not supported.'); + } + break; + case Currency::XAF: + if (! isset($otherData['country'])) { + $this->logger->error('Franco Momo Service::country parameter is required.'); + throw new \InvalidArgumentException('Franco Momo Service: country parameter is required.'); + } + if (! in_array($otherData['country'], $this->supported_countries_franco)) { + $this->logger->error('Franco Momo Service::country passed is not supported.'); + throw new \InvalidArgumentException('Franco Momo Service: country passed is not supported.'); + } + break; } return true; } /** - * @param \stdClass $response - * @param array $payload + * @param \stdClass $response + * @param array $payload * @return array */ private function handleAuthState(\stdClass $response, array $payload): array diff --git a/src/Service/Mpesa.php b/src/Service/Mpesa.php index 5ac7a02..3949ae2 100644 --- a/src/Service/Mpesa.php +++ b/src/Service/Mpesa.php @@ -8,7 +8,7 @@ use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\MpesaEventHandler; use Flutterwave\Traits\Group\Charge; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Mpesa extends Service implements Payment { @@ -22,23 +22,23 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new MpesaEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new MpesaEventHandler($config); } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function initiate(\Flutterwave\Payload $payload): array + public function initiate(\Flutterwave\Entities\Payload $payload): array { return $this->charge($payload); } /** - * @throws Exception + * @throws ClientExceptionInterface * @throws \Exception */ - public function charge(\Flutterwave\Payload $payload): array + public function charge(\Flutterwave\Entities\Payload $payload): array { $this->logger->notice('Charging via Mpesa ...'); @@ -63,9 +63,9 @@ public function charge(\Flutterwave\Payload $payload): array unset($body['country']); unset($body['address']); - MpesaEventHandler::startRecording(); + $this->eventHandler::startRecording(); $request = $this->request($body, 'POST', self::TYPE); - MpesaEventHandler::setResponseTime(); + $this->eventHandler::setResponseTime(); return $this->handleAuthState($request, $body); } @@ -85,7 +85,8 @@ private function handleAuthState(\stdClass $response, array $payload): array return [ 'status' => $response->data->status, 'transactionId' => $response->data->id, - 'dev_instruction' => 'The customer should authorize the payment on their Phones via the Mpesa. status is pending', + 'dev_instruction' => 'The customer should authorize the payment on their Phones + via the Mpesa. status is pending', 'instruction' => 'Please kindly authorize the payment on your Mobile phone', 'mode' => $mode, ]; diff --git a/src/Service/Otps.php b/src/Service/Otps.php index b4fc496..35f8763 100644 --- a/src/Service/Otps.php +++ b/src/Service/Otps.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Otps extends Service { use EventTracker; + private string $name = 'otps'; public function __construct(?ConfigInterface $config = null) @@ -19,7 +20,7 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function create(\Flutterwave\Payload $payload): \stdClass { @@ -62,7 +63,7 @@ public function validate(?string $otp = null, ?string $reference = null): \stdCl $body = ['otp' => $otp]; $this->logger->notice('OTP Service::Validating OTP.'); self::startRecording(); - $response = $this->request($body, 'POST', $this->name."/{$reference}/validate"); + $response = $this->request($body, 'POST', $this->name . "/{$reference}/validate"); $this->logger->notice('OTP Service::Validated OTP Successfully.'); self::setResponseTime(); return $response; @@ -71,28 +72,38 @@ public function validate(?string $otp = null, ?string $reference = null): \stdCl private function checkPayloadOTP(\Flutterwave\Payload $payload): void { if (! $payload->has('length')) { - throw new \InvalidArgumentException("OTP Service:: Required Parameter 'length'. - This is Integer length of the OTP being generated. Expected values are between 5 and 7."); + throw new \InvalidArgumentException( + "OTP Service:: Required Parameter 'length'. + This is Integer length of the OTP being generated. Expected values are between 5 and 7." + ); } if (! $payload->has('customer')) { - throw new \InvalidArgumentException("OTP Service:: Required Parameter 'customer'. - This is customer object used to include the recipient information."); + throw new \InvalidArgumentException( + "OTP Service:: Required Parameter 'customer'. + This is customer object used to include the recipient information." + ); } if (! $payload->has('sender')) { - throw new \InvalidArgumentException("OTP Service:: Required Parameter 'sender'. - This is your merchant/business name. It would display when the OTP is sent."); + throw new \InvalidArgumentException( + "OTP Service:: Required Parameter 'sender'. + This is your merchant/business name. It would display when the OTP is sent." + ); } if (! $payload->has('send')) { - throw new \InvalidArgumentException("OTP Service:: Required Parameter 'send'. - Set to true to send otp to customer.."); + throw new \InvalidArgumentException( + "OTP Service:: Required Parameter 'send'. + Set to true to send otp to customer.." + ); } if (! $payload->has('medium')) { - throw new \InvalidArgumentException("OTP Service:: Required Parameter 'medium'. - Pass the medium you want your customers to receive the OTP on. Expected values are sms, email and whatsapp."); + throw new \InvalidArgumentException( + "OTP Service:: Required Parameter 'medium'. + Pass the medium you want your customers to receive the OTP on. Expected values are sms, email and whatsapp." + ); } } } diff --git a/src/Service/Payload.php b/src/Service/Payload.php index 37c22f8..06d9318 100644 --- a/src/Service/Payload.php +++ b/src/Service/Payload.php @@ -4,65 +4,28 @@ namespace Flutterwave\Service; -use Flutterwave\Payload as Load; +use Flutterwave\Factories\PayloadFactory as Factory; +/** + * Class Payload. + * + * @deprecated use \Flutterwave\Factories\PayloadFactory instead + */ class Payload { - protected array $requiredParams = [ - 'amount','tx_ref','currency','customer', - ]; - - public function create(array $data): Load + private Factory $payloadFactory; + public function __construct() { - $check = $this->validSuppliedData($data); - if (! $check['result']) { - throw new \InvalidArgumentException("".$check['missing_param'].''.' is required in the payload'); - } - - $currency = $data['currency']; - $amount = $data['amount']; - $customer = $data['customer']; - $redirectUrl = $data['redirectUrl'] ?? null; - $otherData = $data['additionalData'] ?? null; - $phone_number = $data['phone'] ?? null; - - if (isset($data['pin']) && ! empty($data['pin'])) { - $otherData['pin'] = $data['pin']; - } - - $payload = new Load(); - - if (! \is_null($phone_number)) { - $payload->set('phone', $phone_number); - } - - $tx_ref = $data['tx_ref'] ?? $payload->generateTxRef(); - -// $payload->set('phone_number', $phone_number); // customer factory handles that - $payload->set('currency', $currency); - $payload->set('amount', $amount); - $payload->set('tx_ref', $tx_ref); - $payload->set('customer', $customer); - $payload->set('redirect_url', $redirectUrl); - $payload->set('otherData', $otherData); + $this->payloadFactory = new Factory(); + } - return $payload; + public function create(array $data): \Flutterwave\Entities\Payload + { + return $this->payloadFactory->create($data); } public function validSuppliedData(array $data): array { - $params = $this->requiredParams; - - foreach ($params as $param) { - if (! array_key_exists($param, $data)) { - return ['missing_param' => $param, 'result' => false]; - } - } - - if (! $data['customer'] instanceof \Flutterwave\Customer) { - return ['missing_param' => 'customer', 'result' => false]; - } - - return ['missing_param' => null, 'result' => true]; + return $this->payloadFactory->validSuppliedData($data); } } diff --git a/src/Service/PaymentPlan.php b/src/Service/PaymentPlan.php index 0f433af..26c016f 100644 --- a/src/Service/PaymentPlan.php +++ b/src/Service/PaymentPlan.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class PaymentPlan extends Service { use EventTracker; + private array $requiredParams = [ 'amount','name','interval','duration', ]; @@ -21,15 +22,16 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function create(\Flutterwave\Payload $payload): \stdClass { $payload = $payload->toArray(); foreach ($this->requiredParams as $param) { if (! array_key_exists($param, $payload)) { - $this->logger->error("Payment Plan Service::The required parameter {$param} is not present in payload"); - throw new \InvalidArgumentException("Payment Plan Service:The required parameter {$param} is not present in payload"); + $msg = "The required parameter {$param} is not present in payload"; + $this->logger->error("Payment Plan Service::" . $msg); + throw new \InvalidArgumentException("Payment Plan Service:" . $msg); } } @@ -44,19 +46,19 @@ public function create(\Flutterwave\Payload $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function get(string $id): \stdClass { $this->logger->notice("Payment Plan Service::Retrieving a Plan ({$id})."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}"); + $response = $this->request(null, 'GET', $this->name . "/{$id}"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function list(): \stdClass { @@ -68,7 +70,7 @@ public function list(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function update(string $id, \Flutterwave\Payload $payload): \stdClass { @@ -80,19 +82,19 @@ public function update(string $id, \Flutterwave\Payload $payload): \stdClass $this->logger->notice("Payment Plan Service::Updating Plan id:({$id})"); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function cancel(string $id): \stdClass { $this->logger->notice("Payment Plan Service::Canceling Plan id:({$id})"); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/cancel"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/cancel"); self::setResponseTime(); return $response; } diff --git a/src/Service/PayoutSubaccount.php b/src/Service/PayoutSubaccount.php index 73b45ab..e63dcaf 100644 --- a/src/Service/PayoutSubaccount.php +++ b/src/Service/PayoutSubaccount.php @@ -7,7 +7,8 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\PayoutSubaccoutEventHandler; use Flutterwave\Payload; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; +use stdClass; class PayoutSubaccount extends Service { @@ -19,7 +20,7 @@ public function __construct(?ConfigInterface $config = null) { parent::__construct($config); $endpoint = $this->name; - $this->url = $this->baseUrl.'/'.$endpoint; + $this->url = $this->baseUrl . '/' . $endpoint; $this->eventHandler = new PayoutSubaccoutEventHandler(); } @@ -41,9 +42,11 @@ public function confirmPayload(Payload $payload): array } /** - * @throws Exception + * @param Payload $payload + * @return stdClass + * @throws ClientExceptionInterface */ - public function create(Payload $payload): \stdClass + public function create(Payload $payload): stdClass { $this->logger->notice('PSA Service::Creating new Payout Subaccount.'); $body = $this->confirmPayload($payload); @@ -55,9 +58,9 @@ public function create(Payload $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function list(): \stdClass + public function list(): stdClass { $this->eventHandler::startRecording(); $response = $this->request(null, 'GET'); @@ -66,18 +69,18 @@ public function list(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function get(string $account_reference): \stdClass { $this->eventHandler::startRecording(); - $response = $this->request(null, 'GET', "/{$account_reference}"); + $response = $this->request(null, 'GET', "/$account_reference"); $this->eventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function update(string $account_reference, Payload $payload): \stdClass { @@ -94,7 +97,7 @@ public function update(string $account_reference, Payload $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function fetchTransactions(string $account_reference): \stdClass { @@ -105,7 +108,7 @@ public function fetchTransactions(string $account_reference): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function fetchAvailableBalance(string $account_reference, string $currency = 'NGN'): \stdClass { @@ -116,9 +119,9 @@ public function fetchAvailableBalance(string $account_reference, string $currenc } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function fetchStaticVirtualAccounts(string $account_reference, string $currency = 'NGN'): \stdClass + public function fetchStaticVirtualAccounts(string $account_reference, string $currency = 'NGN'): stdClass { $this->eventHandler::startRecording(); $response = $this->request(null, 'GET', "/{$account_reference}/static-account?currency={$currency}"); diff --git a/src/Service/Preauth.php b/src/Service/Preauth.php index f8565b9..ec954dc 100644 --- a/src/Service/Preauth.php +++ b/src/Service/Preauth.php @@ -7,8 +7,9 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\PreEventHandler; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Preauth extends Service implements Payment { @@ -22,14 +23,15 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $this->cardService = new CardPayment($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint; + $this->url = $this->baseUrl . '/' . $endpoint; $this->eventHandler = new PreEventHandler(); } /** - * @throws Exception + * @param Payload $payload + * @return array|null */ - public function initiate(\Flutterwave\Payload $payload): ?array + public function initiate(Payload $payload): array { $this->logger->info('Preauth Service::Updated Payload...'); $payload->set('preauthorize', 1); @@ -39,9 +41,9 @@ public function initiate(\Flutterwave\Payload $payload): ?array } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function charge(\Flutterwave\Payload $payload): ?array + public function charge(Payload $payload): array { PreEventHandler::startRecording(); $response = $this->cardService->initiate($payload); @@ -55,24 +57,24 @@ public function save(callable $callback): void } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function capture(string $flw_ref, string $method = 'card', string $amount = '0'): array { $method = strtolower($method); switch ($method) { - case 'paypal': - $data = [ - 'flw_ref' => $flw_ref, - ]; - $this->logger->info("Preauth Service::Capturing PayPal Payment with FLW_REF:{$flw_ref}..."); - $response = $this->request($data, 'POST', '/paypal-capture'); - break; - default: - $data = ['amount' => $amount]; - $this->logger->info("Preauth Service::Capturing Payment with FLW_REF:{$flw_ref}..."); - $response = $this->request($data, 'POST', "/{$flw_ref}/capture"); - break; + case 'paypal': + $data = [ + 'flw_ref' => $flw_ref, + ]; + $this->logger->info("Preauth Service::Capturing PayPal Payment with FLW_REF:{$flw_ref}..."); + $response = $this->request($data, 'POST', '/paypal-capture'); + break; + default: + $data = ['amount' => $amount]; + $this->logger->info("Preauth Service::Capturing Payment with FLW_REF:{$flw_ref}..."); + $response = $this->request($data, 'POST', "/{$flw_ref}/capture"); + break; } $data['message'] = null; @@ -94,27 +96,27 @@ public function capture(string $flw_ref, string $method = 'card', string $amount } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function void(string $flw_ref, string $method = 'card'): array { $method = strtolower($method); switch ($method) { - case 'paypal': - $data = [ - 'flw_ref' => $flw_ref, - ]; - $this->logger->info("Preauth Service::Voiding Payment with FLW_REF:{$flw_ref}..."); - PreEventHandler::startRecording(); - $response = $this->request($data, 'POST', '/paypal-void'); - PreEventHandler::setResponseTime(); - break; - default: - PreEventHandler::startRecording(); - $this->logger->info("Preauth Service::Voiding Payment with FLW_REF:{$flw_ref}..."); - PreEventHandler::setResponseTime(); - $response = $this->request(null, 'POST', "/{$flw_ref}/void"); - break; + case 'paypal': + $data = [ + 'flw_ref' => $flw_ref, + ]; + $this->logger->info("Preauth Service::Voiding Payment with FLW_REF:{$flw_ref}..."); + PreEventHandler::startRecording(); + $response = $this->request($data, 'POST', '/paypal-void'); + PreEventHandler::setResponseTime(); + break; + default: + PreEventHandler::startRecording(); + $this->logger->info("Preauth Service::Voiding Payment with FLW_REF:{$flw_ref}..."); + PreEventHandler::setResponseTime(); + $response = $this->request(null, 'POST', "/{$flw_ref}/void"); + break; } $data['message'] = null; @@ -136,7 +138,7 @@ public function void(string $flw_ref, string $method = 'card'): array } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function refund(string $flw_ref): array { diff --git a/src/Service/Service.php b/src/Service/Service.php index d065d68..0ad3760 100644 --- a/src/Service/Service.php +++ b/src/Service/Service.php @@ -5,20 +5,26 @@ namespace Flutterwave\Service; use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Contract\FactoryInterface; use Flutterwave\Contract\ServiceInterface; +use Flutterwave\Config\ForkConfig; +use Flutterwave\Factories\CustomerFactory as Customer; +use Flutterwave\Factories\PayloadFactory as Payload; use Flutterwave\Helper\Config; -use GuzzleHttp\ClientInterface; -use GuzzleHttp\Exception\GuzzleException; +use Flutterwave\Helper\EnvVariables; +use Psr\Http\Client\ClientInterface; use InvalidArgumentException; -use function is_null; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Log\LoggerInterface; use stdClass; +use function is_null; + class Service implements ServiceInterface { public const ENDPOINT = ''; - public ?Payload $payload; - public ?Customer $customer; + public ?FactoryInterface $payload; + public ?FactoryInterface $customer; protected string $baseUrl; protected LoggerInterface $logger; protected ConfigInterface $config; @@ -37,8 +43,8 @@ public function __construct(?ConfigInterface $config = null) $this->http = $this->config->getHttp(); $this->logger = $this->config->getLoggerInstance(); $this->secret = $this->config->getSecretKey(); - $this->url = $this->config::getBaseUrl().'/'; - $this->baseUrl = $this->config::getBaseUrl(); + $this->url = EnvVariables::BASE_URL . '/'; + $this->baseUrl = EnvVariables::BASE_URL; } public function getName(): string @@ -47,55 +53,69 @@ public function getName(): string } /** - * @param array|null $data - * @param string $verb - * @param string $additionalurl + * @param array|null $data + * @param string $verb + * @param string $additionalurl * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ - protected function request(?array $data = null, string $verb = 'GET', string $additionalurl = ''): stdClass - { + public function request( + ?array $data = null, + string $verb = 'GET', + string $additionalurl = '', + bool $overrideUrl = false + ): stdClass { + $secret = $this->config->getSecretKey(); + $url = $this->getUrl($overrideUrl, $additionalurl); switch ($verb) { - case 'POST': - $response = $this->http->request('POST', $this->url.$additionalurl, [ - 'debug' => false, # TODO: turn to false on release. - 'headers' => [ - 'Authorization' => "Bearer $secret", - 'Content-Type' => 'application/json', - ], - 'json' => $data, - ]); - break; - case 'PUT': - $response = $this->http->request('PUT', $this->url.$additionalurl, [ - 'debug' => false, # TODO: turn to false on release. - 'headers' => [ - 'Authorization' => "Bearer $secret", - 'Content-Type' => 'application/json', - ], - 'json' => $data ?? [], - ]); - break; - case 'DELETE': - $response = $this->http->request('DELETE', $this->url.$additionalurl, [ - 'debug' => false, - 'headers' => [ - 'Authorization' => "Bearer $secret", - 'Content-Type' => 'application/json', - ], - ]); - break; - default: - $response = $this->http->request('GET', $this->url.$additionalurl, [ - 'debug' => false, - 'headers' => [ - 'Authorization' => "Bearer $secret", - 'Content-Type' => 'application/json', - ], - ]); - break; + case 'POST': + $response = $this->http->request( + 'POST', $url, [ + 'debug' => false, // TODO: turn to false on release. + 'headers' => [ + 'Authorization' => "Bearer $secret", + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ] + ); + break; + case 'PUT': + $response = $this->http->request( + 'PUT', $url, [ + 'debug' => false, // TODO: turn to false on release. + 'headers' => [ + 'Authorization' => "Bearer $secret", + 'Content-Type' => 'application/json', + ], + 'json' => $data ?? [], + ] + ); + break; + case 'DELETE': + $response = $this->http->request( + 'DELETE', $url, [ + 'debug' => false, + 'headers' => [ + 'Authorization' => "Bearer $secret", + 'Content-Type' => 'application/json', + ], + ] + ); + break; + default: + $response = $this->http->request( + 'GET', $url, [ + 'debug' => false, + 'headers' => [ + 'Authorization' => "Bearer $secret", + 'Content-Type' => 'application/json', + ], + ] + ); + break; } $body = $response->getBody()->getContents(); @@ -116,14 +136,34 @@ protected function checkTransactionId($transactionId): void private static function bootstrap(?ConfigInterface $config = null): void { if (is_null($config)) { - require __DIR__.'/../../setup.php'; - $config = Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER['ENV'] - ); + include __DIR__ . '/../../setup.php'; + + if ('composer' === $flutterwave_installation) { + $config = Config::setUp( + $keys[Config::SECRET_KEY], + $keys[Config::PUBLIC_KEY], + $keys[Config::ENCRYPTION_KEY], + $keys[Config::ENV] + ); + } + + if ('manual' === $flutterwave_installation) { + $config = ForkConfig::setUp( + $keys[Config::SECRET_KEY], + $keys[Config::PUBLIC_KEY], + $keys[Config::ENCRYPTION_KEY], + $keys[Config::ENV] + ); + } } self::$spareConfig = $config; } + private function getUrl(bool $overrideUrl, string $additionalurl): string + { + if ($overrideUrl) { + return $additionalurl; + } + + return $this->url . $additionalurl; + } } diff --git a/src/Service/Settlement.php b/src/Service/Settlement.php index 8f56990..ab4ad05 100644 --- a/src/Service/Settlement.php +++ b/src/Service/Settlement.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Settlement extends Service { use EventTracker; + private string $name = 'settlements'; public function __construct(?ConfigInterface $config = null) { @@ -18,19 +19,19 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function get(string $id): \stdClass { $this->logger->notice("Settlement Service::Retrieving Settlement [{$id}]."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}"); + $response = $this->request(null, 'GET', $this->name . "/{$id}"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function list(): \stdClass { diff --git a/src/Service/Subscription.php b/src/Service/Subscription.php index 40a154c..48322d6 100644 --- a/src/Service/Subscription.php +++ b/src/Service/Subscription.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Subscription extends Service { use EventTracker; + private string $name = 'subscriptions'; public function __construct(?ConfigInterface $config = null) { @@ -18,7 +19,7 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function list(): \stdClass { @@ -30,25 +31,25 @@ public function list(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function activate(string $id): \stdClass { $this->logger->notice("Subscription Service::Activating a Subscriptions [{$id}]."); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/activate"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/activate"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function deactivate(string $id): \stdClass { $this->logger->notice("Subscription Service::Deactivating a Subscriptions [{$id}]."); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/cancel"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/cancel"); self::setResponseTime(); return $response; } diff --git a/src/Service/TokenizedCharge.php b/src/Service/TokenizedCharge.php index 548c82d..ab080e0 100644 --- a/src/Service/TokenizedCharge.php +++ b/src/Service/TokenizedCharge.php @@ -8,7 +8,7 @@ use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\TkEventHandler; use Flutterwave\Traits\Group\Charge; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class TokenizedCharge extends Service implements Payment { @@ -21,14 +21,14 @@ public function __construct(?ConfigInterface $config = null) { parent::__construct($config); $endpoint = "tokenized-{$this->getEndpoint()}"; - $this->url = $this->baseUrl.'/'.$endpoint; + $this->url = $this->baseUrl . '/' . $endpoint; $this->eventHandler = new TkEventHandler(); } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function initiate(\Flutterwave\Payload $payload) + public function initiate(\Flutterwave\Entities\Payload $payload): array { $this->logger->notice('Tokenize Service::Initiating Card Payment...'); if (! $this->checkPayloadIsValid($payload, 'token')) { @@ -42,11 +42,11 @@ public function initiate(\Flutterwave\Payload $payload) } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function charge(\Flutterwave\Payload $payload): array + public function charge(\Flutterwave\Entities\Payload $payload): array { - # format the customer object to extract the first_name and the last name. + // format the customer object to extract the first_name and the last name. $customer = $payload->get('customer')->toArray(); $fullname = $customer['fullname']; $names = explode(' ', $fullname); diff --git a/src/Service/Transactions.php b/src/Service/Transactions.php index 14439ac..ea40d0e 100644 --- a/src/Service/Transactions.php +++ b/src/Service/Transactions.php @@ -7,13 +7,14 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\TransactionVerificationEventHandler; use Flutterwave\Traits\ApiOperations\Post; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class Transactions extends Service { use Post; + public const ENDPOINT = 'transactions'; - public const REFUND_PATH = '/:id'.'/refund'; + public const REFUND_PATH = '/:id' . '/refund'; public const MULTI_REFUND_ENDPOINT = '/refunds'; public const REFUND_DETAILS_PATH = 'refunds/:id'; public const TRANSACTION_FEE_PATH = '/fee'; @@ -36,17 +37,17 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function verify(string $transactionId): \stdClass { $this->checkTransactionId($transactionId); - $this->logger->notice('Transaction Service::Verifying Transaction...'.$transactionId); + $this->logger->notice('Transaction Service::Verifying Transaction...' . $transactionId); TransactionVerificationEventHandler::startRecording(); $response = $this->request( null, 'GET', - self::ENDPOINT."/{$transactionId}/verify", + self::ENDPOINT . "/{$transactionId}/verify", ); TransactionVerificationEventHandler::setResponseTime(); @@ -54,23 +55,23 @@ public function verify(string $transactionId): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function verifyWithTxref(string $tx_ref): \stdClass { - $this->logger->notice('Transaction Service::Verifying Transaction...'.$tx_ref); + $this->logger->notice('Transaction Service::Verifying Transaction...' . $tx_ref); TransactionVerificationEventHandler::startRecording(); $response = $this->request( null, 'GET', - self::ENDPOINT.'/verify_by_reference?tx_ref='.$tx_ref, + self::ENDPOINT . '/verify_by_reference?tx_ref=' . $tx_ref, ); TransactionVerificationEventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function refund(string $trasanctionId): \stdClass { @@ -80,14 +81,14 @@ public function refund(string $trasanctionId): \stdClass $response = $this->request( null, 'GET', - self::ENDPOINT."/{$trasanctionId}/refund", + self::ENDPOINT . "/{$trasanctionId}/refund", ); TransactionVerificationEventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getAllTransactions(): \stdClass { @@ -103,7 +104,7 @@ public function getAllTransactions(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getRefundInfo(string $trasanctionId): \stdClass { @@ -120,10 +121,13 @@ public function getRefundInfo(string $trasanctionId): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ - public function getTransactionFee(string $amount, string $currency = 'NGN', string $payment_type = 'card'): \stdClass - { + public function getTransactionFee( + string $amount, + string $currency = 'NGN', + string $payment_type = 'card' + ): \stdClass { if (! $amount) { $msg = 'Please pass a valid amount'; $this->logger->warning($msg); @@ -151,14 +155,14 @@ public function getTransactionFee(string $amount, string $currency = 'NGN', stri $response = $this->request( null, 'GET', - self::ENDPOINT."/fee?{$query}", + self::ENDPOINT . "/fee?{$query}", ); TransactionVerificationEventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function resendFailedHooks(string $transactionId): \stdClass { @@ -168,31 +172,33 @@ public function resendFailedHooks(string $transactionId): \stdClass $response = $this->request( null, 'GET', - self::ENDPOINT."/{$transactionId}/resend-hook", + self::ENDPOINT . "/{$transactionId}/resend-hook", ); TransactionVerificationEventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function retrieveTimeline(string $transactionId): \stdClass { $this->checkTransactionId($transactionId); - $this->logger->notice("Transaction Service::Retrieving Transaction Timeline: TransactionId => {$transactionId}"); + $this->logger->notice( + "Transaction Service::Retrieving Transaction Timeline: TransactionId => {$transactionId}" + ); TransactionVerificationEventHandler::startRecording(); $response = $this->request( null, 'GET', - self::ENDPOINT."/{$transactionId}/timeline", + self::ENDPOINT . "/{$transactionId}/timeline", ); TransactionVerificationEventHandler::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function validate(string $otp, string $flw_ref): \stdClass { @@ -203,12 +209,12 @@ public function validate(string $otp, string $flw_ref): \stdClass ] ); - $this->logger->notice('Transaction Service::Validating Transaction ...'.$logData); + $this->logger->notice('Transaction Service::Validating Transaction ...' . $logData); $data = [ 'otp' => $otp, 'flw_ref' => $flw_ref, -// "type" => "card" //default would be card + // "type" => "card" //default would be card ]; return $this->request( diff --git a/src/Service/Transfer.php b/src/Service/Transfer.php index 2b574c3..efffb46 100644 --- a/src/Service/Transfer.php +++ b/src/Service/Transfer.php @@ -7,15 +7,17 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\TransferEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; +use Psr\Http\Client\ClientExceptionInterface; use stdClass; class Transfer extends Service implements Payment { use Charge; + public const TYPE = 'transfers'; private TransferEventHandler $eventHandler; private string $name = 'transfers'; @@ -23,21 +25,21 @@ class Transfer extends Service implements Payment 'amount', 'currency', ]; private array $requiredParamsRate = [ - 'amount', 'destination_currency'. 'source_currency', + 'amount', 'destination_currency' . 'source_currency', ]; public function __construct(?ConfigInterface $config = null) { parent::__construct($config); $endpoint = 'transfers'; - $this->url = $this->baseUrl.'/'.$endpoint; - $this->eventHandler = new TransferEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint; + $this->eventHandler = new TransferEventHandler($config); } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function initiate(Payload $payload): array { @@ -50,9 +52,9 @@ public function initiate(Payload $payload): array } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function charge(Payload $payload): array { @@ -86,7 +88,7 @@ private function handleInitiationResponse(stdClass $data): array 'bank_code' => $root->bank_code, 'full_name' => $root->full_name, 'currency' => $root->currency, -// 'debit_currency' => $root->debit_currency, + // 'debit_currency' => $root->debit_currency, 'reference' => $root->reference, 'amount' => $root->amount, 'status' => $root->status, @@ -100,32 +102,33 @@ public function save(callable $callback): void } /** - * @param string|null $transactionId + * @param string|null $transactionId * @return stdClass * retry a previously failed transfer. * - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function retry(?string $transactionId): stdClass { $this->checkTransactionId($transactionId); $this->logger->notice("Transfer Service::Retrieving Settlement [$transactionId]."); $this->eventHandler::startRecording(); - $response = $this->request(null, 'POST', $this->name."/$transactionId/retries"); + $response = $this->request(null, 'POST', $this->name . "/$transactionId/retries"); $this->eventHandler::setResponseTime(); return $response; } /** - * @param Payload $payload + * @param Payload $payload * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function createBulk(Payload $payload): stdClass { if (! $payload->has('bulk_data')) { - $this->logger->error('Transfer Service::Bulk Payload is empty. Pass a filled array'); - throw new InvalidArgumentException('Transfer Service::Bulk Payload is currently empty. Pass a filled array'); + $msg = 'Bulk Payload is empty. Pass a filled array'; + $this->logger->error('Transfer Service::' . $msg); + throw new InvalidArgumentException('Transfer Service::' . $msg); } $body = $payload->toArray(); @@ -138,22 +141,22 @@ public function createBulk(Payload $payload): stdClass } /** - * @param string $id + * @param string $id * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function get(string $id): stdClass { $this->logger->notice("Transfer Service::Retrieving Transfer id:($id)"); $this->eventHandler::startRecording(); - $response = $this->request(null, 'GET', $this->name."/$id"); + $response = $this->request(null, 'GET', $this->name . "/$id"); $this->eventHandler::setResponseTime(); return $response; } /** * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function getAll(): stdClass { @@ -165,16 +168,17 @@ public function getAll(): stdClass } /** - * @param array $params + * @param array $params * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function getFee(array $params = []): stdClass { foreach ($this->requiredParamsFee as $param) { if (! array_key_exists($param, $params)) { - $this->logger->error("Transfer Service::the following param is required to get transfer fee: $param"); - throw new InvalidArgumentException("Transfer Service::the following param is required to get transfer fee: $param"); + $msg = "the following param is required to get transfer fee: $param"; + $this->logger->error("Transfer Service::$msg"); + throw new InvalidArgumentException("Transfer Service::$msg"); } } @@ -187,9 +191,9 @@ public function getFee(array $params = []): stdClass } /** - * @param string $id + * @param string $id * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function getRetry(string $id): stdClass { @@ -202,9 +206,9 @@ public function getRetry(string $id): stdClass } /** - * @param string $batch_id + * @param string $batch_id * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function getBulk(string $batch_id): stdClass { @@ -217,16 +221,17 @@ public function getBulk(string $batch_id): stdClass } /** - * @param array $params + * @param array $params * @return stdClass - * @throws GuzzleException + * @throws ClientExceptionInterface */ public function getRates(array $params): stdClass { foreach ($this->requiredParamsRate as $param) { if (! array_key_exists($param, $params)) { - $this->logger->error("Transfer Service::the following param is required to get transfer rate: $param"); - throw new InvalidArgumentException("Transfer Service::the following param is required to get transfer rate: $param"); + $msg = "the following param is required to get transfer rate: $param"; + $this->logger->error("Transfer Service::$msg"); + throw new InvalidArgumentException("Transfer Service::$msg"); } } diff --git a/src/Service/Ussd.php b/src/Service/Ussd.php index 39f1ef1..bd082e3 100644 --- a/src/Service/Ussd.php +++ b/src/Service/Ussd.php @@ -7,9 +7,10 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Contract\Payment; use Flutterwave\EventHandlers\UssdEventHandler; -use Flutterwave\Payload; +use Flutterwave\Entities\Payload; use Flutterwave\Traits\Group\Charge; use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Client\ClientExceptionInterface; class Ussd extends Service implements Payment { @@ -45,27 +46,27 @@ public function __construct(?ConfigInterface $config = null) parent::__construct($config); $endpoint = $this->getEndpoint(); - $this->url = $this->baseUrl.'/'.$endpoint.'?type='; - $this->eventHandler = new UssdEventHandler(); + $this->url = $this->baseUrl . '/' . $endpoint . '?type='; + $this->eventHandler = new UssdEventHandler($config); } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws \Exception + * @throws ClientExceptionInterface */ - public function initiate(\Flutterwave\Payload $payload): array + public function initiate(Payload $payload): array { $this->logger->info('Ussd Service::Initiated Ussd Charge'); return $this->charge($payload); } /** - * @param Payload $payload + * @param Payload $payload * @return array - * @throws GuzzleException + * @throws ClientExceptionInterface */ - public function charge(\Flutterwave\Payload $payload): array + public function charge(Payload $payload): array { $otherData = $payload->get('otherData'); @@ -86,8 +87,9 @@ public function charge(\Flutterwave\Payload $payload): array $bank = $otherData['account_bank']; if (! array_key_exists($bank, $this->supported_banks)) { - $this->logger->error('USSD Service: We do not support your bank. please kindly use another. '); - throw new \InvalidArgumentException('USSD Service: We do not support your bank. please kindly use another. '); + $msg = 'We do not support your bank. please kindly use another. '; + $this->logger->error('USSD Service:' . $msg); + throw new \InvalidArgumentException('USSD Service:' . $msg); } $payload = $payload->toArray(); @@ -98,11 +100,11 @@ public function charge(\Flutterwave\Payload $payload): array unset($body['country']); unset($body['address']); - UssdEventHandler::startRecording(); + $this->eventHandler::startRecording(); $this->logger->info('Ussd Service::Generating Ussd Code'); $request = $this->request($body, 'POST', self::TYPE); $this->logger->info('Ussd Service::Generated Ussd Code Successfully'); - UssdEventHandler::setResponseTime(); + $this->eventHandler::setResponseTime(); return $this->handleAuthState($request, $body); } @@ -113,8 +115,8 @@ public function save(callable $callback): void } /** - * @param \stdClass $response - * @param array $payload + * @param \stdClass $response + * @param array $payload * @return array * @throws \Exception */ diff --git a/src/Service/VirtualAccount.php b/src/Service/VirtualAccount.php index 8248f9a..9442241 100644 --- a/src/Service/VirtualAccount.php +++ b/src/Service/VirtualAccount.php @@ -6,11 +6,12 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\EventHandlers\EventTracker; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class VirtualAccount extends Service { use EventTracker; + private string $name = 'virtual-account-numbers'; public function __construct(?ConfigInterface $config = null) { @@ -18,7 +19,7 @@ public function __construct(?ConfigInterface $config = null) } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function create(array $payload): \stdClass { @@ -26,8 +27,9 @@ public function create(array $payload): \stdClass //check email and bvn are in payload if (! isset($payload['email']) || ! isset($payload['bvn'])) { - $this->logger->error('VirtualAccount Service::The required parameter email or bvn is not present in payload'); - throw new \InvalidArgumentException('The required parameter email or bvn is not present in payload'); + $msg = 'The required parameter email or bvn is not present in payload'; + $this->logger->error('VirtualAccount Service::' . $msg); + throw new \InvalidArgumentException($msg); } $this->logger->notice('VirtualAccount Service::Payload Confirmed.'); @@ -38,7 +40,7 @@ public function create(array $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function createBulk(array $payload): \stdClass { @@ -49,8 +51,9 @@ public function createBulk(array $payload): \stdClass $this->logger->notice('VirtualAccount Service::Creating Bulk Virtual Accounts.'); //check accounts and email are in payload if (! isset($payload['accounts']) || ! isset($payload['email'])) { - $this->logger->error('VirtualAccount Service::The required parameter accounts or email is not present in payload'); - throw new \InvalidArgumentException('The required parameter accounts or email is not present in payload'); + $msg = 'The required parameter accounts or email is not present in payload'; + $this->logger->error('VirtualAccount Service::' . $msg); + throw new \InvalidArgumentException($msg); } $this->logger->notice('VirtualAccount Service:: Payload Confirmed [Bulk].'); @@ -62,7 +65,7 @@ public function createBulk(array $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function get($order_ref): \stdClass { @@ -74,7 +77,7 @@ public function get($order_ref): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getBulk($batch_id): \stdClass { @@ -86,14 +89,15 @@ public function getBulk($batch_id): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function update(array $payload): \stdClass { //check email and bvn are in payload if (! isset($payload['order_ref']) || ! isset($payload['bvn'])) { - $this->logger->error('VirtualAccount Service::The required parameter order_ref or bvn is not present in payload'); - throw new \InvalidArgumentException('The required parameter order_ref or bvn is not present in payload'); + $msg = 'The required parameter order_ref or bvn is not present in payload'; + $this->logger->error('VirtualAccount Service::' . $msg); + throw new \InvalidArgumentException($msg); } $order_ref = $payload['order_ref']; @@ -108,7 +112,7 @@ public function update(array $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function delete($order_ref): \stdClass { diff --git a/src/Service/VirtualCard.php b/src/Service/VirtualCard.php index 07e7933..76097b9 100644 --- a/src/Service/VirtualCard.php +++ b/src/Service/VirtualCard.php @@ -8,13 +8,25 @@ use Flutterwave\EventHandlers\EventTracker; use Flutterwave\Payload; use InvalidArgumentException; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; class VirtualCard extends Service { use EventTracker; + private string $name = 'virtual-cards'; - private array $requiredParams = [ 'currency', 'amount', 'first_name', 'last_name', 'date_of_birth','email', 'phone', 'title', 'gender' ]; + private array $requiredParams = [ + 'currency', + 'amount', + 'first_name', + 'last_name', + 'date_of_birth', + 'email', + 'phone', + 'title', + 'gender' + ]; + private array $requiredParamsFund = ['debit_currency','amount']; public function __construct(?ConfigInterface $config = null) { @@ -25,8 +37,9 @@ public function confirmPayload(Payload $payload): array { foreach ($this->requiredParams as $param) { if (! $payload->has($param)) { - $this->logger->error("VirtualCard Service::The required parameter {$param} is not present in payload"); - throw new InvalidArgumentException("The required parameter {$param} is not present in payload"); + $msg = "The required parameter {$param} is not present in payload"; + $this->logger->error("VirtualCard Service::$msg"); + throw new InvalidArgumentException($msg); } } @@ -34,7 +47,7 @@ public function confirmPayload(Payload $payload): array } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function create(Payload $payload): \stdClass { @@ -48,19 +61,19 @@ public function create(Payload $payload): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function get(string $id): \stdClass { $this->logger->notice("VirtualCard Service::Retrieving Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}"); + $response = $this->request(null, 'GET', $this->name . "/{$id}"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function list(): \stdClass { @@ -72,81 +85,82 @@ public function list(): \stdClass } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function fund(string $id, array $data): \stdClass { foreach ($this->requiredParamsFund as $param) { if (! array_key_exists($param, $data)) { - $this->logger->error("Misc Service::The following parameter is missing to check balance history: {$param}"); - throw new \InvalidArgumentException("The following parameter is missing to check balance history: {$param}"); + $msg = "The following parameter is missing to check balance history: {$param}"; + $this->logger->error("Misc Service::$msg"); + throw new \InvalidArgumentException($msg); } } $this->logger->notice("VirtualCard Service::Funding Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request($data, 'POST', $this->name."/{$id}/fund"); + $response = $this->request($data, 'POST', $this->name . "/{$id}/fund"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function withdraw(string $id, string $amount = '0'): \stdClass { $this->logger->notice("VirtualCard Service::Withdrawing from Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request([ 'amount' => $amount ], 'POST', $this->name."/{$id}/withdraw"); + $response = $this->request([ 'amount' => $amount ], 'POST', $this->name . "/{$id}/withdraw"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function block(string $id): \stdClass { $this->logger->notice("VirtualCard Service::Blocking Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/status/block"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/status/block"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function unblock(string $id): \stdClass { $this->logger->notice("VirtualCard Service::Unblocking Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/status/unblock"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/status/unblock"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function terminate(string $id): \stdClass { $this->logger->notice("VirtualCard Service::Terminating Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request(null, 'PUT', $this->name."/{$id}/terminate"); + $response = $this->request(null, 'PUT', $this->name . "/{$id}/terminate"); self::setResponseTime(); return $response; } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function getTransactions(string $id, array $options = ['index' => 0, 'size' => 20]): \stdClass { $query = http_build_query($options); $this->logger->notice("VirtualCard Service::Retrieving transaction for Virtual Card [{$id}]."); self::startRecording(); - $response = $this->request(null, 'GET', $this->name."/{$id}/transactions?{$query}"); + $response = $this->request(null, 'GET', $this->name . "/{$id}/transactions?{$query}"); self::setResponseTime(); return $response; } diff --git a/src/Traits/ApiOperations/Delete.php b/src/Traits/ApiOperations/Delete.php index e3fd677..1040c11 100644 --- a/src/Traits/ApiOperations/Delete.php +++ b/src/Traits/ApiOperations/Delete.php @@ -4,17 +4,16 @@ namespace Flutterwave\Traits\ApiOperations; -use Unirest\Request; +use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Service\Service as Http; +use Psr\Http\Client\ClientExceptionInterface; trait Delete { - public function delURL(string $url): string + public function deleteURL(ConfigInterface $config, string $url): string { - $bearerTkn = 'Bearer ' . $this->secretKey; - $headers = ['Content-Type' => 'application/json', 'Authorization' => $bearerTkn]; - //$body = Body::json($data); - $path = $this->baseUrl . '/' . $this->end_point; - $response = Request::delete($path . $url, $headers); - return $response->raw_body; + $response = (new Http($config))->request(null, 'DELETE', $url); + + return ''; } } diff --git a/src/Traits/ApiOperations/Get.php b/src/Traits/ApiOperations/Get.php index 640d8c8..89dd415 100644 --- a/src/Traits/ApiOperations/Get.php +++ b/src/Traits/ApiOperations/Get.php @@ -4,18 +4,27 @@ namespace Flutterwave\Traits\ApiOperations; -use Unirest\Request; +use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Exception\ApiException; +use Flutterwave\Service\Service as Http; +use Psr\Http\Client\ClientExceptionInterface; +use stdClass; trait Get { - public function getURL(string $url): string + /** + * @param ConfigInterface $config + * @param string $url + * @return stdClass + * @throws ClientExceptionInterface + * @throws ApiException + */ + public function getURL(ConfigInterface $config, string $url): stdClass { - // make request to endpoint using unirest. - $bearerTkn = 'Bearer ' . $this->secretKey; - $headers = ['Content-Type' => 'application/json', 'Authorization' => $bearerTkn]; - //$body = Body::json($data); - $path = $this->baseUrl . '/' . $this->end_point; - $response = Request::get($path . $url, $headers); - return $response->raw_body; // Unparsed body + $response = (new Http($config))->request(null, 'GET', $url); + if ($response->status === 'success') { + return $response; + } + throw new ApiException($response->message); } } diff --git a/src/Traits/ApiOperations/Post.php b/src/Traits/ApiOperations/Post.php index 801392e..d9b0215 100644 --- a/src/Traits/ApiOperations/Post.php +++ b/src/Traits/ApiOperations/Post.php @@ -4,25 +4,23 @@ namespace Flutterwave\Traits\ApiOperations; -use Unirest\Exception; -use Unirest\Request; -use Unirest\Request\Body; +use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Service\Service as Http; +use Psr\Http\Client\ClientExceptionInterface; trait Post { /** - * @param array $data + * @param ConfigInterface $config + * @param array $data * - * @throws Exception + * @return string + * @throws ClientExceptionInterface */ - public function postURL(array $data): string + public function postURL(ConfigInterface $config, array $data): string { - // make request to endpoint using unirest - $bearerTkn = 'Bearer ' . $this->config->getSecretKey(); - $headers = ['Content-Type' => 'application/json', 'Authorization' => $bearerTkn]; - $body = Body::json($data); - $url = $this->baseUrl . '/' . $this->end_point; - $response = Request::post($url, $headers, $body); - return $response->raw_body; // Unparsed body + $response = (new Http($config))->request($data, 'POST', $this->end_point); + + return ''; } } diff --git a/src/Traits/ApiOperations/Put.php b/src/Traits/ApiOperations/Put.php index 48560cb..65d9f7b 100644 --- a/src/Traits/ApiOperations/Put.php +++ b/src/Traits/ApiOperations/Put.php @@ -4,24 +4,23 @@ namespace Flutterwave\Traits\ApiOperations; -use Unirest\Exception; -use Unirest\Request; -use Unirest\Request\Body; +use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Service\Service as Http; +use Psr\Http\Client\ClientExceptionInterface; trait Put { /** - * @param array $data + * @param ConfigInterface $config + * @param array $data * - * @throws Exception + * @return string + * @throws ClientExceptionInterface */ - public function putURL(array $data): string + public function putURL(ConfigInterface $config, array $data): string { - $bearerTkn = 'Bearer ' . $this->secretKey; - $headers = ['Content-Type' => 'application/json', 'Authorization' => $bearerTkn]; - $body = Body::json($data); - $url = $this->baseUrl . '/' . $this->end_point; - $response = Request::put($url, $headers, $body); - return $response->raw_body; + $response = (new Http($config))->request($data, 'PUT', $this->end_point); + + return ''; } } diff --git a/src/Traits/Group/Charge.php b/src/Traits/Group/Charge.php index b3be91e..160bc27 100644 --- a/src/Traits/Group/Charge.php +++ b/src/Traits/Group/Charge.php @@ -5,7 +5,7 @@ namespace Flutterwave\Traits\Group; use Flutterwave\Service\Transactions; -use Unirest\Exception; +use Psr\Http\Client\ClientExceptionInterface; trait Charge { @@ -15,7 +15,7 @@ public function getEndpoint(): string } /** - * @throws Exception + * @throws ClientExceptionInterface */ public function verify(?string $transactionId = null): \stdClass { @@ -26,7 +26,7 @@ public function verify(?string $transactionId = null): \stdClass return (new Transactions($this->config))->verify($transactionId); } - private function checkPayloadIsValid(\Flutterwave\Payload $payload, string $criteria): bool + private function checkPayloadIsValid(\Flutterwave\Entities\Payload $payload, string $criteria): bool { $this->logger->notice('Charge Group::Verifying Payload ...'); //if does not payload contains $criteria :: false diff --git a/src/Traits/PayloadOperations/Prepare.php b/src/Traits/PayloadOperations/Prepare.php index 754a2f2..f265f0e 100644 --- a/src/Traits/PayloadOperations/Prepare.php +++ b/src/Traits/PayloadOperations/Prepare.php @@ -16,11 +16,7 @@ trait Prepare public function createReferenceNumber(): self { $this->logger->notice('Generating Reference Number....'); - if ($this->overrideTransactionReference) { - $this->txref = $this->transactionPrefix; - } else { - $this->txref = uniqid($this->transactionPrefix); - } + $this->txref = uniqid($this->transactionPrefix); $this->logger->notice('Generated Reference Number....' . $this->txref); return $this; } diff --git a/src/Traits/Setup/Configure.php b/src/Traits/Setup/Configure.php index be8de8d..981fea9 100644 --- a/src/Traits/Setup/Configure.php +++ b/src/Traits/Setup/Configure.php @@ -6,21 +6,38 @@ use Flutterwave\Contract\ConfigInterface; use Flutterwave\Helper\Config; +use Flutterwave\Config\ForkConfig; trait Configure { public static function bootstrap(?ConfigInterface $config = null): void { if (\is_null($config)) { - require __DIR__.'/../../../setup.php'; - $config = Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER['ENV'] - ); + include __DIR__ . '/../../../setup.php'; + + if ('composer' === $flutterwave_installation) { + $config = Config::setUp( + $keys[Config::SECRET_KEY], + $keys[Config::PUBLIC_KEY], + $keys[Config::ENCRYPTION_KEY], + $keys[Config::ENV] + ); + } + + if ('manual' === $flutterwave_installation) { + $config = ForkConfig::setUp( + $keys[ForkConfig::SECRET_KEY], + $keys[ForkConfig::PUBLIC_KEY], + $keys[ForkConfig::ENCRYPTION_KEY], + $keys[ForkConfig::ENV] + ); + } + } + + if (\is_null(self::$config)) { + self::$config = $config; } - self::$config = $config; - self::$methods = require __DIR__ . '/../../Util/methods.php'; + + self::$methods = include __DIR__ . '/../../Util/methods.php'; } } diff --git a/src/Util/Currency.php b/src/Util/Currency.php index e5c4c88..21977f1 100644 --- a/src/Util/Currency.php +++ b/src/Util/Currency.php @@ -14,8 +14,10 @@ class Currency public const ZMW = 'ZMW'; public const EUR = 'EUR'; public const GHS = 'GHS'; - public const TNZ = 'TNZ'; + public const TZS = 'TZS'; public const RWF = 'RWF'; public const XAF = 'XAF'; public const XOF = 'XOF'; + public const EGP = 'EGP'; + public const GBP = 'GBP'; } diff --git a/src/Util/methods.php b/src/Util/methods.php index 0a656a2..3e5fd20 100644 --- a/src/Util/methods.php +++ b/src/Util/methods.php @@ -16,6 +16,10 @@ use Flutterwave\Service\TokenizedCharge; use Flutterwave\Service\Transfer; use Flutterwave\Service\Ussd; +use Flutterwave\Service\GooglePay; +use Flutterwave\Service\Enaira; +use Flutterwave\Service\Fawry; + //use Flutterwave\Service\PayPal; //use Flutterwave\Service\Remita; //use Flutterwave\Service\VoucherPayment; @@ -35,6 +39,9 @@ 'tokenize' => TokenizedCharge::class, 'transfer' => Transfer::class, 'ussd' => Ussd::class, + 'google' => GooglePay::class, + 'enaira' => Enaira::class, + 'fawry' => Fawry::class // "paypal" => PayPal::class, // "remita" => Remita::class, // "voucher" => VoucherPayment::class, diff --git a/tests/Resources/setup/Config.php b/tests/Resources/Setup/Config.php similarity index 77% rename from tests/Resources/setup/Config.php rename to tests/Resources/Setup/Config.php index ed7ae94..eea6a86 100644 --- a/tests/Resources/setup/Config.php +++ b/tests/Resources/Setup/Config.php @@ -5,12 +5,14 @@ namespace Flutterwave\Test\Resources\Setup; use Flutterwave\Contract\ConfigInterface; +use Flutterwave\Helper\EnvVariables; use GuzzleHttp\Client; -use GuzzleHttp\ClientInterface; + use function is_null; use Monolog\Handler\RotatingFileHandler; use Monolog\Logger; use Psr\Log\LoggerInterface; +use Psr\Http\Client\ClientInterface; class Config implements ConfigInterface { @@ -18,8 +20,6 @@ class Config implements ConfigInterface public const SECRET_KEY = 'SECRET_KEY'; public const ENCRYPTION_KEY = 'ENCRYPTION_KEY'; public const ENV = 'ENV'; - public const VERSION = 'v3'; - public const BASE_URL = 'https://api.flutterwave.com/'.self::VERSION; public const DEFAULT_PREFIX = 'FW|PHP'; public const LOG_FILE_NAME = 'flutterwave-php.log'; protected Logger $logger; @@ -31,19 +31,21 @@ class Config implements ConfigInterface private ClientInterface $http; private string $enc; - private function __construct(string $secretKey, string $publicKey, string $encryptKey, string $env) + private function __construct( + string $secretKey, + string $publicKey, + string $encryptKey, + string $env + ) { $this->secret = $secretKey; $this->public = $publicKey; $this->enc = $encryptKey; $this->env = $env; - - $this->http = new Client([ - 'base_uri' => $this->getBaseUrl(), - 'timeout' => 60, - ]); - - $log = new Logger('Flutterwave/PHP'); + # when creating a custom config, you may choose to use other dependencies here. + # http-client - Guzzle, logger - Monolog. + $this->http = new Client(['base_uri' => EnvVariables::BASE_URL, 'timeout' => 60 ]); + $log = new Logger('Flutterwave/PHP'); // making use of Monolog; $this->logger = $log; $log->pushHandler(new RotatingFileHandler(self::LOG_FILE_NAME, 90)); } @@ -58,6 +60,7 @@ public static function setUp(string $secretKey, string $publicKey, string $enc, public function getHttp(): ClientInterface { + # for custom implementation, please ensure the return $this->http ?? new Client(); } @@ -76,11 +79,6 @@ public function getPublicKey(): string return $this->public; } - public static function getBaseUrl(): string - { - return self::BASE_URL; - } - public function getSecretKey(): string { return $this->secret; diff --git a/tests/Unit/Service/AccountTest.php b/tests/Unit/Service/AccountTest.php index d3a629c..ed31cbd 100644 --- a/tests/Unit/Service/AccountTest.php +++ b/tests/Unit/Service/AccountTest.php @@ -3,69 +3,95 @@ namespace Unit\Service; use PHPUnit\Framework\TestCase; +use Flutterwave\Flutterwave; use Flutterwave\Util\AuthMode; use Flutterwave\Util\Currency; -use Flutterwave\Helper\Config; +use Flutterwave\Test\Resources\Setup\Config; class AccountTest extends TestCase { protected function setUp(): void { - \Flutterwave\Flutterwave::bootstrap(); + Flutterwave::bootstrap(); } -// public function testAuthModeReturn() -// { -// //currently returning "Sorry, we could not connect to your bank"; -// -// $data = [ -// "amount" => 2000, -// "currency" => Currency::NGN, -// "tx_ref" => uniqid().time(), -// "additionalData" => [ -// "account_details" => [ -// "account_bank" => "044", -// "account_number" => "0690000034", -// "country" => "NG" -// ] -// ], -// ]; -// -// $accountpayment = \Flutterwave\Flutterwave::create("account"); -// $customerObj = $accountpayment->customer->create([ -// "full_name" => "Temi Adekunle", -// "email" => "developers@flutterwavego.com", -// "phone" => "+2349067985861" -// ]); -// -// $data['customer'] = $customerObj; -// $payload = $accountpayment->payload->create($data); -// $this->expectException(\Exception::class); -// $result = $accountpayment->initiate($payload); -// -// //check mode returned is either OTP or Redirect -//// $this->assertTrue($result['mode'] === AuthMode::OTP || $result['mode'] === AuthMode::REDIRECT ); -// } + public function testNgnAuthModeReturn() + { + //currently returning "Sorry, we could not connect to your bank"; + + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "additionalData" => [ + "account_details" => [ + "account_bank" => "044", + "account_number" => "0690000034", + "country" => "NG" + ] + ], + ]; + + $accountpayment = \Flutterwave\Flutterwave::create("account"); + $customerObj = $accountpayment->customer->create([ + "full_name" => "Temi Adekunle", + "email" => "developers@flutterwavego.com", + "phone" => "+2349067985861" + ]); + + $data['customer'] = $customerObj; + $payload = $accountpayment->payload->create($data); + $result = $accountpayment->initiate($payload); + $this->assertTrue( $result['mode'] === AuthMode::REDIRECT ); + } + + public function testInvalidParam() + { + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "additionalData" => null, + ]; -// public function testInvalidParam() -// { -// $data = [ -// "amount" => 2000, -// "currency" => Currency::NGN, -// "tx_ref" => uniqid().time(), -// "additionalData" => null, -// ]; -// -// $accountpayment = \Flutterwave\Flutterwave::create("account"); -// $customerObj = $accountpayment->customer->create([ -// "full_name" => "Jake Jesulayomi Ola", -// "email" => "developers@flutterwavego.com", -// "phone" => "+2349067985861" -// ]); -// -// $data['customer'] = $customerObj; -// $payload = $accountpayment->payload->create($data); -// $this->expectException(\InvalidArgumentException::class); -// $result = $accountpayment->initiate($payload); -// } + $accountpayment = \Flutterwave\Flutterwave::create("account"); + $customerObj = $accountpayment->customer->create([ + "full_name" => "Jake Jesulayomi Ola", + "email" => "developers@flutterwavego.com", + "phone" => "+2349067985861" + ]); + + $data['customer'] = $customerObj; + $payload = $accountpayment->payload->create($data); + $this->expectException(\InvalidArgumentException::class); + $result = $accountpayment->initiate($payload); + } + + public function testUKBankAccountAuthMode() { + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "additionalData" => [ + "account_details" => [ + "account_bank" => "044", + "account_number" => "0690000034", + "country" => "UK" //or EU + ] + ], + ]; + + $accountpayment = \Flutterwave\Flutterwave::create("account"); + $customerObj = $accountpayment->customer->create([ + "full_name" => "Jake Jesulayomi Ola", + "email" => "developers@flutterwavego.com", + "phone" => "+2349067985861" + ]); + + $data['customer'] = $customerObj; + $payload = $accountpayment->payload->create($data); + $result = $accountpayment->initiate($payload); + + $this->assertTrue( $result['mode'] === AuthMode::REDIRECT ); + } } \ No newline at end of file diff --git a/tests/Unit/Service/AchTest.php b/tests/Unit/Service/AchTest.php index cd5dc79..c10db98 100644 --- a/tests/Unit/Service/AchTest.php +++ b/tests/Unit/Service/AchTest.php @@ -4,13 +4,16 @@ use Flutterwave\Util\AuthMode; use PHPUnit\Framework\TestCase; +use Flutterwave\Flutterwave; use Flutterwave\Util\Currency; +use Flutterwave\Test\Resources\Setup\Config; + class AchTest extends TestCase { protected function setUp(): void { - \Flutterwave\Flutterwave::bootstrap(); + Flutterwave::bootstrap(); } // public function testAuthModeReturnRedirect() @@ -22,7 +25,7 @@ protected function setUp(): void // "redirectUrl" => "https://google.com" // ]; // -// $achpayment = \Flutterwave\Flutterwave::create("ach"); +// $achpayment = Flutterwave::create("ach"); // $customerObj = $achpayment->customer->create([ // "full_name" => "Olaobaju Jesulayomi Abraham", // "email" => "vicomma@gmail.com", @@ -37,25 +40,25 @@ protected function setUp(): void // $this->assertSame(AuthMode::REDIRECT, $result['mode']); // } -// public function testBankPermittedToMerchant() -// { -// $data = [ -// "amount" => 2000, -// "currency" => Currency::ZAR, -// "tx_ref" => uniqid().time(), -// "redirectUrl" => "https://google.com" -// ]; -// -// $achpayment = \Flutterwave\Flutterwave::create("ach"); -// $customerObj = $achpayment->customer->create([ -// "full_name" => "Olaobaju Jesulayomi Abraham", -// "email" => "vicomma@gmail.com", -// "phone" => "+2349067985861" -// ]); -// -// $data['customer'] = $customerObj; -// $payload = $achpayment->payload->create($data); -// $this->expectExceptionMessage("This bank payment option is not permitted to the merchant"); -// $result = $achpayment->initiate($payload); -// } + // public function testBankPermittedToMerchant() + // { + // $data = [ + // "amount" => 2000, + // "currency" => Currency::ZAR, + // "tx_ref" => uniqid().time(), + // "redirectUrl" => "https://google.com" + // ]; + + // $achpayment = Flutterwave::create("ach"); + // $customerObj = $achpayment->customer->create([ + // "full_name" => "Olaobaju Jesulayomi Abraham", + // "email" => "vicomma@gmail.com", + // "phone" => "+2349067985861" + // ]); + + // $data['customer'] = $customerObj; + // $payload = $achpayment->payload->create($data); + // $this->expectExceptionMessage("This bank payment option is not permitted to the merchant"); + // $result = $achpayment->initiate($payload); + // } } \ No newline at end of file diff --git a/tests/Unit/Service/ApplePayTest.php b/tests/Unit/Service/ApplePayTest.php index 6c2b6d4..db72ced 100644 --- a/tests/Unit/Service/ApplePayTest.php +++ b/tests/Unit/Service/ApplePayTest.php @@ -8,57 +8,56 @@ class ApplePayTest extends TestCase { -// protected function setUp(): void -// { -// \Flutterwave\Flutterwave::bootstrap(); -// } -// -// public function testAuthModeReturnRedirect() -// { -// $data = [ -// "amount" => 2000, -// "currency" => Currency::NGN, -// "tx_ref" => uniqid().time(), -// "redirectUrl" => "https://example.com" -// ]; -// -// $applepayment = \Flutterwave\Flutterwave::create("apple"); -// $customerObj = $applepayment->customer->create([ -// "full_name" => "Olaobaju Jesulayomi Abraham", -// "email" => "vicomma@gmail.com", -// "phone" => "+2349067985861" -// ]); -// -// $data['customer'] = $customerObj; -// $payload = $applepayment->payload->create($data); -// $result = $applepayment->initiate($payload); -// -// $this->assertSame(AuthMode::REDIRECT, $result['mode']); -// } -// -// public function testInvalidParams() -// { -// $data = [ -// "amount" => 2000, -// "currency" => Currency::NGN, -// "tx_ref" => uniqid().time(), -// "redirectUrl" => "https://example.com" -// ]; -// -// $applepayment = \Flutterwave\Flutterwave::create("apple"); -// //no customer object; -// $this->expectException(\InvalidArgumentException::class); -// $payload = $applepayment->payload->create($data); -// $result = $applepayment->initiate($payload); -// } -// -// public function testEmptyParamsPassed() -// { -// $data = []; -// $applepayment = \Flutterwave\Flutterwave::create("apple"); -// $this->expectException(\InvalidArgumentException::class); -// $payload = $applepayment->payload->create($data); -// $result = $applepayment->initiate($payload); -// -// } + protected function setUp(): void + { + \Flutterwave\Flutterwave::bootstrap(); + } + + public function testAuthModeReturnRedirect() + { + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" + ]; + + $applepayment = \Flutterwave\Flutterwave::create("apple"); + $customerObj = $applepayment->customer->create([ + "full_name" => "Olaobaju Jesulayomi Abraham", + "email" => "vicomma@gmail.com", + "phone" => "+2349060085861" + ]); + + $data['customer'] = $customerObj; + $payload = $applepayment->payload->create($data); + $result = $applepayment->initiate($payload); + + $this->assertSame(AuthMode::REDIRECT, $result['mode']); + } + + public function testInvalidParams() + { + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" + ]; + + $applepayment = \Flutterwave\Flutterwave::create("apple"); + $this->expectException(\InvalidArgumentException::class); + $payload = $applepayment->payload->create($data); + $result = $applepayment->initiate($payload); + } + + public function testEmptyParamsPassed() + { + $data = []; + $applepayment = \Flutterwave\Flutterwave::create("apple"); + $this->expectException(\InvalidArgumentException::class); + $payload = $applepayment->payload->create($data); + $result = $applepayment->initiate($payload); + + } } \ No newline at end of file diff --git a/tests/Unit/Service/BankTest.php b/tests/Unit/Service/BankTest.php index c7cea09..4e5a8a1 100644 --- a/tests/Unit/Service/BankTest.php +++ b/tests/Unit/Service/BankTest.php @@ -11,14 +11,7 @@ class BankTest extends TestCase public Banks $service; protected function setUp(): void { - $this->service = new Banks( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + $this->service = new Banks(); } public function testRetrievingBankByCountry() diff --git a/tests/Unit/Service/BankTransferTest.php b/tests/Unit/Service/BankTransferTest.php index 80959c1..559a9b5 100644 --- a/tests/Unit/Service/BankTransferTest.php +++ b/tests/Unit/Service/BankTransferTest.php @@ -13,14 +13,7 @@ class BankTransferTest extends TestCase { protected function setUp(): void { - Flutterwave::bootstrap( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + Flutterwave::bootstrap(); } public function testAuthModeReturnBankTransfer() @@ -44,4 +37,28 @@ public function testAuthModeReturnBankTransfer() $result = $btpayment->initiate($payload); $this->assertSame(AuthMode::BANKTRANSFER, $result['mode']); } + + + public function testExpiryOption() + { + $data = [ + "amount" => 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://google.com", + "expires" => 3600 + ]; + + $btpayment = Flutterwave::create("bank-transfer"); + $customerObj = $btpayment->customer->create([ + "full_name" => "Olaobaju Jesulayomi Abraham", + "email" => "developers@flutterwavego.com", + "phone" => "+2349067985011" + ]); + + $data['customer'] = $customerObj; + $payload = $btpayment->payload->create($data); + $result = $btpayment->initiate($payload); + $this->assertTrue(isset($result['account_expiration'])); + } } \ No newline at end of file diff --git a/tests/Unit/Service/BillTest.php b/tests/Unit/Service/BillTest.php index b1c5efa..a2b0cba 100644 --- a/tests/Unit/Service/BillTest.php +++ b/tests/Unit/Service/BillTest.php @@ -13,14 +13,7 @@ class BillTest extends \PHPUnit\Framework\TestCase public Bill $service; protected function setUp(): void { - $this->service = new Bill( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + $this->service = new Bill(); } // public function testBillCreation() // { diff --git a/tests/Unit/Service/CardTest.php b/tests/Unit/Service/CardTest.php index 81fe1e8..93c9b5a 100644 --- a/tests/Unit/Service/CardTest.php +++ b/tests/Unit/Service/CardTest.php @@ -13,14 +13,7 @@ class CardTest extends TestCase { protected function setUp(): void { - Flutterwave::bootstrap( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + Flutterwave::bootstrap(); } public function testAuthModeReturnPin() @@ -51,7 +44,7 @@ public function testAuthModeReturnPin() $cardpayment = Flutterwave::create("card"); $customerObj = $cardpayment->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", + "email" => "ol868gjdfjua@gmail.com", "phone" => "+2349067985861" ]); $data['customer'] = $customerObj; @@ -74,8 +67,8 @@ public function testInvalidArgumentExceptionThrowOnNoCardDetails() $cardpayment = Flutterwave::create("card"); $customerObj = $cardpayment->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "ola57679urhfdjf@gmail.com", + "phone" => "+234906792751" ]); $data['customer'] = $customerObj; $payload = $cardpayment->payload->create($data); @@ -112,8 +105,8 @@ public function testAuthModeReturnRedirect() $cardpayment = Flutterwave::create("card"); $customerObj = $cardpayment->customer->create([ "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "email" => "ol868gjdfjua@gmail.com", + "phone" => "+2349062985861" ]); $data['customer'] = $customerObj; $payload = $cardpayment->payload->create($data); @@ -125,42 +118,42 @@ public function testAuthModeReturnRedirect() } - public function testAuthModeReturnAVS() - { - $data = [ - "amount" => 2000, - "currency" => Currency::NGN, - "tx_ref" => "TEST-".uniqid().time(), - "redirectUrl" => "https://www.example.com", - "additionalData" => [ - "subaccounts" => [ - ["id" => "RSA_345983858845935893"] - ], - "meta" => [ - "unique_id" => uniqid().uniqid() - ], - "preauthorize" => false, - "payment_plan" => null, - "card_details" => [ - "card_number" => "4556052704172643", - "cvv" => "899", - "expiry_month" => "01", - "expiry_year" => "23" - ] - ], - ]; - - $cardpayment = Flutterwave::create("card"); - $customerObj = $cardpayment->customer->create([ - "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" - ]); - $data['customer'] = $customerObj; - $payload = $cardpayment->payload->create($data); - $result = $cardpayment->initiate($payload); - $this->assertSame(AuthMode::AVS, $result['mode']); - } + // public function testAuthModeReturnAVS() + // { + // $data = [ + // "amount" => 2000, + // "currency" => Currency::NGN, + // "tx_ref" => "TEST-".uniqid().time(), + // "redirectUrl" => "https://www.example.com", + // "additionalData" => [ + // "subaccounts" => [ + // ["id" => "RSA_345983858845935893"] + // ], + // "meta" => [ + // "unique_id" => uniqid().uniqid() + // ], + // "preauthorize" => false, + // "payment_plan" => null, + // "card_details" => [ + // "card_number" => "4556052704172643", + // "cvv" => "899", + // "expiry_month" => "09", + // "expiry_year" => "32" + // ] + // ], + // ]; + + // $cardpayment = Flutterwave::create("card"); + // $customerObj = $cardpayment->customer->create([ + // "full_name" => "Olaobaju Abraham", + // "email" => "oyudfjmscfka@gmail.com", + // "phone" => "+2349067968461" + // ]); + // $data['customer'] = $customerObj; + // $payload = $cardpayment->payload->create($data); + // $result = $cardpayment->initiate($payload); + // $this->assertSame(AuthMode::AVS, $result['mode']); + // } public function testAuthModelReturnNoauth() { diff --git a/tests/Unit/Service/CheckoutTest.php b/tests/Unit/Service/CheckoutTest.php new file mode 100644 index 0000000..f8680ac --- /dev/null +++ b/tests/Unit/Service/CheckoutTest.php @@ -0,0 +1,123 @@ +paymentHandler = new ModalEventHandler(); + } + + /** + * Tests Inline Setup. + * + * @dataProvider checkoutProvider + */ + public function testCheckoutProcess( + string $modalType, + array $generatedTransactionData, + EventHandlerInterface $paymentHandler, + ConfigInterface $config, + array $request + ){ + $mockClient = $this->createMock(Flutterwave::class); + $mockModal = $this->createMock(Modal::class); + + $mockModal + ->expects($this->exactly(1)) + ->method('with') + ->will($this->returnValue($mockModal)); + + if( 'standard' === $modalType ) { + $mockModal + ->expects($this->exactly(1)) + ->method('getUrl') + ->will($this->returnValue('')); + } else { + $mockModal + ->expects($this->exactly(1)) + ->method('getHtml') + ->will($this->returnValue('')); + } + + $mockClient + ->expects($this->exactly(1)) + ->method('render') + ->with( $modalType ) + ->will($this->returnValue($mockModal)); + + $mockClient + ->expects($this->exactly(1)) + ->method('eventHandler') + ->will($this->returnValue($mockClient)); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + + $controller = new PaymentController( $mockClient , $paymentHandler, $modalType ); + + $controller->process( $request ); + } + + public function checkoutProvider() { + return [ + [ + Modal::STANDARD, + [ "tx_ref" => 'FLW_TEST|' . random_int( 10, 2000) . '|' . uniqid('aMx') ], + new ModalEventHandler(), + ForkConfig::setUp( + $_ENV['SECRET_KEY'], + $_ENV['PUBLIC_KEY'], + $_ENV['ENCRYPTION_KEY'], + $_ENV['ENV'] + ), + [ + 'amount' => 3000, + 'currency' => Currency::NGN, + 'phone_number' => '080000000000', + 'first_name' => 'Abraham', + 'last_name' => 'Olaobaju', + 'success_url' => null, + 'failure_url' => null, + ] + ], + [ + Modal::POPUP, + [ "tx_ref" => 'FLW_TEST|' . random_int( 10, 2000) . '|' . uniqid('mAx') ], + new ModalEventHandler(), + ForkConfig::setUp( + $_ENV['SECRET_KEY'], + $_ENV['PUBLIC_KEY'], + $_ENV['ENCRYPTION_KEY'], + $_ENV['ENV'] + ), + [ + 'amount' => 1500, + 'currency' => Currency::NGN, + 'phone_number' => '08000000000', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'success_url' => null, + 'failure_url' => null, + ] + ] + + + ]; + } + +} \ No newline at end of file diff --git a/tests/Unit/Service/EnairaTest.php b/tests/Unit/Service/EnairaTest.php new file mode 100644 index 0000000..3129783 --- /dev/null +++ b/tests/Unit/Service/EnairaTest.php @@ -0,0 +1,39 @@ + 2000, + "is_token" => 1, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" + ]; + + $payment = \Flutterwave\Flutterwave::create("enaira"); + $customerObj = $payment->customer->create([ + "full_name" => "Flutterwave Developers", + "email" => "olaobaju@gmail.com", + "phone" => "+2349067985861" + ]); + + $data['customer'] = $customerObj; + $payload = $payment->payload->create($data); + $result = $payment->initiate($payload); + $this->assertSame(AuthMode::REDIRECT, $result['mode']); + } +} \ No newline at end of file diff --git a/tests/Unit/Service/FawryTest.php b/tests/Unit/Service/FawryTest.php new file mode 100644 index 0000000..6f68a67 --- /dev/null +++ b/tests/Unit/Service/FawryTest.php @@ -0,0 +1,40 @@ + 2000, + "currency" => Currency::EGP, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" + ]; + + $payment = \Flutterwave\Flutterwave::create("fawry"); + $customerObj = $payment->customer->create([ + "full_name" => "Olaobaju Jesulayomi Abraham", + "email" => "vicomma@gmail.com", + "phone" => "+2349060085861" + ]); + + $data['customer'] = $customerObj; + $payload = $payment->payload->create($data); + $result = $payment->initiate($payload); + + $this->assertSame('fawry_pay', $result['mode']); + } + +} \ No newline at end of file diff --git a/tests/Unit/Service/GooglePayTest.php b/tests/Unit/Service/GooglePayTest.php new file mode 100644 index 0000000..d9e269a --- /dev/null +++ b/tests/Unit/Service/GooglePayTest.php @@ -0,0 +1,38 @@ + 2000, + "currency" => Currency::NGN, + "tx_ref" => uniqid().time(), + "redirectUrl" => "https://example.com" + ]; + + $googlepayment = \Flutterwave\Flutterwave::create("google"); + $customerObj = $googlepayment->customer->create([ + "full_name" => "Olaobaju Jesulayomi Abraham", + "email" => "vicomma@gmail.com", + "phone" => "+2349060085861" + ]); + + $data['customer'] = $customerObj; + $payload = $googlepayment->payload->create($data); + $result = $googlepayment->initiate($payload); + + $this->assertSame(AuthMode::REDIRECT, $result['mode']); + } +} \ No newline at end of file diff --git a/tests/Unit/Service/MomoTest.php b/tests/Unit/Service/MomoTest.php index 24a4d49..0afd9ab 100644 --- a/tests/Unit/Service/MomoTest.php +++ b/tests/Unit/Service/MomoTest.php @@ -12,14 +12,7 @@ class MomoTest extends TestCase { protected function setUp(): void { - Flutterwave::bootstrap( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + Flutterwave::bootstrap(); } public function testAuthModeRwandaRedirect(){ @@ -49,6 +42,32 @@ public function testAuthModeRwandaRedirect(){ $this->assertSame(AuthMode::REDIRECT,$result['mode']); } + + public function testInitiateTanzaniaRedirect(){ + $data = [ + "amount" => 2000, + "currency" => Currency::TZS, + "tx_ref" => uniqid().time(), + "redirectUrl" => null, + "additionalData" => [ + "network" => "VODAFONE", + ] + ]; + + $momopayment = \Flutterwave\Flutterwave::create("momo"); + $customerObj = $momopayment->customer->create([ + "full_name" => "Abiodun Abrahams", + "email" => "developers@flutterwavego.com", + "phone" => "+2349067982061" + ]); + + $data['customer'] = $customerObj; + + $payload = $momopayment->payload->create($data); + $result = $momopayment->initiate($payload); + $this->assertSame('pending',$result['data_to_save']['status']); + } + public function testAuthModeGhanaRedirect(){ $data = [ "amount" => 2000, diff --git a/tests/Unit/Service/PaymentPlanTest.php b/tests/Unit/Service/PaymentPlanTest.php index 16cee35..8db92b4 100644 --- a/tests/Unit/Service/PaymentPlanTest.php +++ b/tests/Unit/Service/PaymentPlanTest.php @@ -13,14 +13,7 @@ class PaymentPlanTest extends TestCase public PaymentPlan $service; protected function setUp(): void { - $this->service = new PaymentPlan( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + $this->service = new PaymentPlan(); } public function testPlanCreation() diff --git a/tests/Unit/Service/TransferTest.php b/tests/Unit/Service/TransferTest.php index 1cabb23..7da166f 100644 --- a/tests/Unit/Service/TransferTest.php +++ b/tests/Unit/Service/TransferTest.php @@ -13,14 +13,7 @@ class TransferTest extends TestCase public Transfer $service; protected function setUp(): void { - $this->service = new Transfer( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + $this->service = new Transfer(); } public function testInitiatingTransfer() @@ -42,9 +35,9 @@ public function testInitiatingTransfer() ]; $customerObj = $this->service->customer->create([ - "full_name" => "Olaobaju Abraham", - "email" => "olaobajua@gmail.com", - "phone" => "+2349067985861" + "full_name" => "Time Squad", + "email" => "ol868gjdfjua@gmail.com", + "phone" => "+234900000001" ]); $data['customer'] = $customerObj; $payload = $this->service->payload->create($data); diff --git a/tests/Unit/Service/UssdTest.php b/tests/Unit/Service/UssdTest.php index 89d188a..4a98a85 100644 --- a/tests/Unit/Service/UssdTest.php +++ b/tests/Unit/Service/UssdTest.php @@ -12,14 +12,7 @@ class UssdTest extends TestCase { protected function setUp(): void { - Flutterwave::bootstrap( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + Flutterwave::bootstrap(); } // public function testAuthModeReturnUssd() @@ -98,7 +91,7 @@ public function testInvalidBank() $data['customer'] = $customerObj; $payload = $ussdpayment->payload->create($data); - $this->expectExceptionMessage("USSD Service: We do not support your bank. please kindly use another."); + $this->expectExceptionMessage("USSD Service:We do not support your bank. please kindly use another. "); $result = $ussdpayment->initiate($payload); } } \ No newline at end of file diff --git a/tests/Unit/Service/VirtualAccountTest.php b/tests/Unit/Service/VirtualAccountTest.php index 402cb59..ed61adb 100644 --- a/tests/Unit/Service/VirtualAccountTest.php +++ b/tests/Unit/Service/VirtualAccountTest.php @@ -14,14 +14,7 @@ class VirtualAccountTest extends TestCase public VirtualAccount $service; protected function setUp(): void { - $this->service = new VirtualAccount( - Config::setUp( - $_SERVER[Config::SECRET_KEY], - $_SERVER[Config::PUBLIC_KEY], - $_SERVER[Config::ENCRYPTION_KEY], - $_SERVER[Config::ENV] - ) - ); + $this->service = new VirtualAccount(); } public function testVirtualAccountCreation() @@ -29,6 +22,8 @@ public function testVirtualAccountCreation() $payload = [ "email" => "kennyio@gmail.com", "bvn" => "12345678901", + "amount" => "3000", + "currency" => "NGN" ]; $response = $this->service->create($payload); diff --git a/tests/Unit/Service/VirtualCardTest.php b/tests/Unit/Service/VirtualCardTest.php index a2d6e8c..e573892 100644 --- a/tests/Unit/Service/VirtualCardTest.php +++ b/tests/Unit/Service/VirtualCardTest.php @@ -5,113 +5,113 @@ use Flutterwave\Payload; use Flutterwave\Service\VirtualCard; use Flutterwave\Util\Currency; +use Flutterwave\Test\Resources\Setup\Config; use PHPUnit\Framework\TestCase; + class VirtualCardTest extends TestCase { -// public function testVirtualCardCreation() -// { -// $payload = new Payload(); -// $service = new VirtualCard(); -// -// $payload->set("first_name","PHP"); -// $payload->set("last_name","SDK"); -// $payload->set("date_of_birth","1994-03-01"); -// $payload->set("title","Mr"); -// $payload->set("gender","M"); //M or F -// $payload->set("email","developers@flutterwavego.com"); -// $payload->set("currency", Currency::NGN); -// $payload->set("amount", "5000"); -// $payload->set("debit_currency", Currency::NGN); -// $payload->set("phone", "+234505394568"); -// $payload->set("billing_name", "Abraham Ola"); -// $payload->set("firstname", "Abraham"); -// $response = $service->create($payload); -// $this->assertTrue(property_exists( -// $response, "data") && !empty($response->data->id) && isset($response->data->card_pan) -// ); -// -// return $response->data->id; -// } -// -// public function testRetrievingAllVirtualCards() -// { -// $service = new VirtualCard(); -// $request = $service->list(); -// $this->assertTrue(property_exists($request,'data') && \is_array($request->data)); -// } -// -// /** -// * @depends testVirtualCardCreation -// */ -// public function testRetrievingVirtualCard(string $id) -// { -// $service = new VirtualCard(); -// $request = $service->get($id); -// $this->assertTrue(property_exists($request,'data') && !empty($request->data->id)); -// } -// -// -// /** -// * @depends testVirtualCardCreation -// */ -// public function testVirtualCardFund(string $id) -// { -// $data = [ -// "amount"=>"3500", -// "debit_currency" => Currency::NGN -// ]; -// $service = new VirtualCard(); -// $request = $service->fund($id, $data); -// $this->assertTrue(property_exists($request,'data') && $request->message == "Card funded successfully"); -// } -// -// /** -// * @depends testVirtualCardCreation -// */ -// public function testVirtualCardWithdraw(string $id) -// { -// $card_id = $id; -// $amount = "3500"; -// $service = new VirtualCard(); -// $request = $service->withdraw($card_id,$amount); -// $this->assertTrue(property_exists($request,'data')); -// } -// -//// /** -//// * @depends testVirtualCardCreation -//// */ -//// public function testVirtualCardBlock(string $id) -//// { -//// $service = new VirtualCard(); -//// $request = $service->block($id); -//// $this->assertTrue(property_exists($request,'data') && $request->message == "Card blocked successfully"); -//// } -// -// /** -// * @depends testVirtualCardCreation -// */ -// public function testVirtualCardTerminate(string $id) -// { -// $service = new VirtualCard(); -// $request = $service->terminate($id); -// $this->assertTrue(property_exists($request,'data') && $request->message == "Card terminated successfully"); -// } -// -// /** -// * @depends testVirtualCardCreation -// */ -// public function testRetrievingCardTransactions(string $id) -// { -// $data = [ -// "from" => "2019-01-01", -// "to" => "2020-01-13", -// "index" => "2", -// "size" => "3" -// ]; -// -// $service = new VirtualCard(); -// $request = $service->getTransactions($id, $data); -// $this->assertTrue(property_exists($request,'data') && $request->message == "Card transactions fetched successfully"); -// } + public VirtualCard $service; + protected function setUp(): void + { + $this->service = new VirtualCard(); + } + +// public function testVirtualCardCreation() +// { +// $payload = new Payload(); + +// $payload->set("first_name","PHP"); +// $payload->set("last_name","SDK"); +// $payload->set("date_of_birth","1994-03-01"); +// $payload->set("title","Mr"); +// $payload->set("gender","M"); //M or F +// $payload->set("email","developers@flutterwavego.com"); +// $payload->set("currency", Currency::NGN); +// $payload->set("amount", "5000"); +// $payload->set("debit_currency", Currency::NGN); +// $payload->set("phone", "+234505394568"); +// $payload->set("billing_name", "Abraham Ola"); +// $payload->set("firstname", "Abraham"); +// $response = $this->service->create($payload); +// $this->assertTrue(property_exists( +// $response, "data") && !empty($response->data->id) && isset($response->data->card_pan) +// ); + +// return $response->data->id; +// } + + public function testRetrievingAllVirtualCards() + { + $request = $this->service->list(); + $this->assertTrue(property_exists($request,'data') && \is_array($request->data)); + } + +// /** +// * @depends testVirtualCardCreation +// */ +// public function testRetrievingVirtualCard(string $id) +// { +// $request = $this->service->get($id); +// $this->assertTrue(property_exists($request,'data') && !empty($request->data->id)); +// } + + +// /** +// * @depends testVirtualCardCreation +// */ +// public function testVirtualCardFund(string $id) +// { +// $data = [ +// "amount"=>"3500", +// "debit_currency" => Currency::NGN +// ]; +// $request = $this->service->fund($id, $data); +// $this->assertTrue(property_exists($request,'data') && $request->message == "Card funded successfully"); +// } + +// /** +// * @depends testVirtualCardCreation +// */ +// public function testVirtualCardWithdraw(string $id) +// { +// $card_id = $id; +// $amount = "3500"; +// $request = $this->service->withdraw($card_id,$amount); +// $this->assertTrue(property_exists($request,'data')); +// } + +// // /** +// // * @depends testVirtualCardCreation +// // */ +// // public function testVirtualCardBlock(string $id) +// // { +// // $request = $this->service->block($id); +// // $this->assertTrue(property_exists($request,'data') && $request->message == "Card blocked successfully"); +// // } + +// /** +// * @depends testVirtualCardCreation +// */ +// public function testVirtualCardTerminate(string $id) +// { +// $request = $this->service->terminate($id); +// $this->assertTrue(property_exists($request,'data') && $request->message == "Card terminated successfully"); +// } + +// /** +// * @depends testVirtualCardCreation +// */ +// public function testRetrievingCardTransactions(string $id) +// { +// $data = [ +// "from" => "2019-01-01", +// "to" => "2020-01-13", +// "index" => "2", +// "size" => "3" +// ]; + +// $request = $this->service->getTransactions($id, $data); +// $this->assertTrue(property_exists($request,'data') && $request->message == "Card transactions fetched successfully"); +// } } \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 36de571..a0eed40 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,7 +14,7 @@ * ####### ############### ######## ######## * ####### ############### ###### ###### * - * Flutterwave API Library for PHP + * Flutterwave Client Library for PHP * * Copyright (c) 2020 Flutterwave inc. * This file is open source and available under the MIT license. @@ -22,4 +22,19 @@ * */ -require_once __DIR__ . '/../vendor/autoload.php'; \ No newline at end of file +# include vendor directory +require_once __DIR__ . '/../vendor/autoload.php'; + +# by pass final definitions. +DG\BypassFinals::enable(); +DG\BypassFinals::setWhitelist([ + '*/src/Library/*', + '*/src/Entities/*', + '*/src/Factories/*', + '*/src/HttpAdapter/*', + '*/src/Controller/*', +]); + +# flutterwave setup. +require_once __DIR__ . '/../setup.php'; +