diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..7133d483 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.scrutinizer.yml export-ignore +/update_currencies.php export-ignore +/tests export-ignore +/docs export-ignore \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0d77ee1a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [barryvdh] diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 00000000..f25a1831 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,59 @@ +name: "PHPUnit tests" + +on: + pull_request: + push: + branches: + - "master" + +jobs: + phpunit: + name: "PHPUnit tests" + + runs-on: "ubuntu-latest" + + strategy: + matrix: + dependencies: + - "highest" + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + - "8.3" + - "8.4" + + include: + - php-version: '7.2' + dependencies: "lowest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "pcov" + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + tools: composer:v2 + + - name: "Install lowest dependencies" + if: ${{ matrix.dependencies == 'lowest' }} + run: "composer update --prefer-lowest --no-interaction --no-progress" + + - name: "Install highest dependencies" + if: ${{ matrix.dependencies == 'highest' }} + run: "composer update --no-interaction --no-progress" + + - name: "Tests (PHPUnit 9)" + if: ${{ matrix.php-version <= '8.0' }} + run: "vendor/bin/phpunit --configuration phpunit9.xml.dist" + + - name: "Tests (PHPUnit 10+)" + if: ${{ matrix.php-version >= '8.1' }} + run: "vendor/bin/phpunit" diff --git a/.gitignore b/.gitignore index 9ab0cfa4..73418ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build /vendor composer.lock composer.phar @@ -5,4 +6,5 @@ phpunit.xml .directory reports/ documents/ -.idea \ No newline at end of file +.idea +.phpunit.result.cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index abb187d5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - hhvm - -# This triggers builds to run on the new TravisCI infrastructure. -# See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ -sudo: false - -## Cache composer -cache: - directories: - - $HOME/.composer/cache - -env: - global: - - setup=basic - -matrix: - include: - - php: 5.3 - env: setup=lowest - - php: 5.5 - env: setup=lowest - -install: - - if [[ $setup = 'basic' ]]; then travis_retry composer install --prefer-dist --no-interaction; fi - - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable; fi - -script: vendor/bin/phpcs --standard=PSR2 src && vendor/bin/phpunit --coverage-text diff --git a/LICENSE b/LICENSE index d49d0a9d..23d85a3b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2013 Adrian Macneil +Copyright (c) Adrian Macneil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 5c6f4127..130d776b 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,48 @@ **Core components for the Omnipay PHP payment processing library** -[![Build Status](https://travis-ci.org/thephpleague/omnipay-common.png?branch=master)](https://travis-ci.org/thephpleague/omnipay-common) -[![Latest Stable Version](https://poser.pugx.org/omnipay/common/version.png)](https://packagist.org/packages/omnipay/common) -[![Total Downloads](https://poser.pugx.org/omnipay/common/d/total.png)](https://packagist.org/packages/omnipay/common) +[![PHPUnit tests](https://github.com/thephpleague/omnipay-common/actions/workflows/phpunit.yml/badge.svg)](https://github.com/thephpleague/omnipay-common/actions/workflows/phpunit.yml) +[![Latest Version on Packagist][ico-version]][link-packagist] +[![Software License][ico-license]](LICENSE) +[![Total Downloads][ico-downloads]][link-downloads] [Omnipay](https://github.com/thephpleague/omnipay) is a framework agnostic, multi-gateway payment -processing library for PHP 5.3+. This package implements common classes required by Omnipay. +processing library for PHP. This package implements common classes required by Omnipay. + +## Documentation + +Please see [https://omnipay.thephpleague.com/](https://omnipay.thephpleague.com/) for the installation & usage documentation. + +## Change log + +Please see [UPGRADE](UPGRADE.md) for more information on how to upgrade to the latest version. ## Support If you are having general issues with Omnipay, we suggest posting on -[Stack Overflow](http://stackoverflow.com/). Be sure to add the -[omnipay tag](http://stackoverflow.com/questions/tagged/omnipay) so it can be easily found. +[Stack Overflow](https://stackoverflow.com/). Be sure to add the +[omnipay tag](https://stackoverflow.com/questions/tagged/omnipay) so it can be easily found. -If you want to keep up to date with release anouncements, discuss ideas for the project, +If you want to keep up to date with release announcements, discuss ideas for the project, or ask more detailed questions, there is also a [mailing list](https://groups.google.com/forum/#!forum/omnipay) which you can subscribe to. If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/thephpleague/omnipay-common/issues), or better yet, fork the library and submit a pull request. + + +## Security + +If you discover any security related issues, please email barryvdh@gmail.com instead of using the issue tracker. + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. + +[ico-version]: https://img.shields.io/packagist/v/omnipay/common.svg?style=flat +[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat +[ico-downloads]: https://img.shields.io/packagist/dt/omnipay/common.svg?style=flat + +[link-packagist]: https://packagist.org/packages/omnipay/common +[link-downloads]: https://packagist.org/packages/omnipay/common diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..973cdff2 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,62 @@ +## Upgrade apps from 2.x to 3.x + - The `redirect()` method no longer calls `exit()` after sending the content. This is up to the developer now. + - It is now possible to use `setAmountInteger(integer $value)` and `setMoney(Money $money)` + - HTTPPlug is used. Guzzle will be installed when using `omnipay/omnipay`, otherwise you need to require your own implementation (see http://docs.php-http.org/en/latest/clients.html) + - The package is renamed from `omnipay/omnipay` to `league/omnipay` and no longer installs all gateways by default. +## Upgrade Gateways from 2.x to 3.x + +The primary difference is the HTTP Client. We are now using HTTPlug (http://httplug.io/) but rely on our own interface. + +### Breaking +- Change typehint from Guzzle ClientInterface to `Omnipay\Common\Http\ClientInterface` +- `$client->get('..')`/`$client->post('..')` etc are removed, you can call `$client->request('GET', '')`. +- No need to call `$request->send()`, requests are sent directly. +- Instead of `$client->createRequest(..)` you can create+send the request directly with `$client->request(..)`. +- When sending a JSON body, convert the body to a string with `json_encode()` and set the correct Content-Type. +- The response is a PSR-7 Response object. You can call `$response->getBody()->getContents()` to get the body as a string. +- `$response->json()` and `$response->xml()` are gone, but you can implement the logic directly. +- An HTTP Client is no longer added by default by `omnipay/common`, but `omnipay/omnipay` will add Guzzle. +Gateways should not rely on Guzzle or other clients directly. +- `$body` should be a string (eg. `http_build_query($data)` or `json_encode($data)` instead of just `$data`). +- The `$headers` parameters should be an `array` (not `null`, but can be empty) + +Examples: +```php +// V2 XML: + $response = $this->httpClient->post($this->endpoint, null, $data)->send(); + $result = $httpResponse->xml(); + +// V3 XML: + $response = $this->httpClient->request('POST', $this->endpoint, [], http_build_query($data)); + $result = simplexml_load_string($httpResponse->getBody()->getContents()); +``` + +```php +// Example JSON request: + + $response = $this->httpClient->request('POST', $this->endpoint, [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], json_encode($data)); + + $result = json_decode($response->getBody()->getContents(), true); +``` + +#### Testing + +PHPUnit is upgraded to PHPUnit 6. Common issues: + +- `setExpectedException()` is removed +```php +// PHPUnit 5: +$this->setExpectedException($class, $message); + +// PHPUnit 6: +$this->expectException($class); +$this->expectExceptionMessage($message); +``` + +- Tests that do not perform any assertions, will be marked as risky. This can be avoided by annotating them with ` @doesNotPerformAssertions` + +- You should remove the `Mockery\Adapter\Phpunit\TestListener` in phpunit.xml.dist + diff --git a/composer.json b/composer.json index 320d2145..be27a33e 100644 --- a/composer.json +++ b/composer.json @@ -17,62 +17,64 @@ "name": "Adrian Macneil", "email": "adrian@adrianmacneil.com" }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + }, + { + "name": "Jason Judge", + "email": "jason.judge@consil.co.uk" + }, + { + "name": "Del" + }, { "name": "Omnipay Contributors", "homepage": "https://github.com/thephpleague/omnipay-common/contributors" } ], "autoload": { - "psr-0": { "Omnipay\\Common\\" : "src/" }, - "classmap": ["src/Omnipay/Omnipay.php"] + "psr-4": { "Omnipay\\Common\\" : "src/Common" }, + "classmap": ["src/Omnipay.php"] + }, + "autoload-dev": { + "psr-4": { "Omnipay\\Common\\" : "tests/Common" }, + "classmap": ["tests/OmnipayTest.php"] }, "require": { - "php": ">=5.3.2", - "guzzle/guzzle": "~3.9", - "symfony/http-foundation": "~2.1|~3.0" + "php": "^7.2|^8", + "php-http/client-implementation": "^1", + "php-http/message": "^1.5", + "php-http/message-factory": "^1.1", + "php-http/discovery": "^1.14", + "symfony/http-foundation": "^2.1|^3|^4|^5|^6|^7", + "moneyphp/money": "^3.1|^4.0.3" }, "require-dev": { - "omnipay/tests": "~2.0", - "squizlabs/php_codesniffer": "~1.5" + "omnipay/tests": "^4.1", + "php-http/mock-client": "^1.6", + "php-http/guzzle7-adapter": "^1", + "squizlabs/php_codesniffer": "^3.8.1", + "http-interop/http-factory-guzzle": "^1.1" }, "extra": { "branch-alias": { - "dev-master": "2.5.x-dev" - }, - "gateways": [ - "AuthorizeNet_AIM", - "AuthorizeNet_SIM", - "Buckaroo_CreditCard", - "Buckaroo_Ideal", - "Buckaroo_PayPal", - "CardSave", - "Coinbase", - "Dummy", - "Eway_Rapid", - "FirstData_Connect", - "GoCardless", - "Manual", - "Migs_ThreeParty", - "Migs_TwoParty", - "Mollie", - "MultiSafepay", - "Netaxept", - "NetBanx", - "PayFast", - "Payflow_Pro", - "PaymentExpress_PxPay", - "PaymentExpress_PxPost", - "PayPal_Express", - "PayPal_Pro", - "Pin", - "SagePay_Direct", - "SagePay_Server", - "SecurePay_DirectPost", - "Stripe", - "TargetPay_Directebanking", - "TargetPay_Ideal", - "TargetPay_Mrcash", - "WorldPay" - ] + "dev-master": "3.1.x-dev" + } + }, + "suggest": { + "league/omnipay": "The default Omnipay package provides a default HTTP Adapter." + }, + "scripts": { + "test": "phpunit", + "check-style": "phpcs -p --standard=PSR2 src/", + "fix-style": "phpcbf -p --standard=PSR2 src/" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/makedoc.sh b/makedoc.sh deleted file mode 100755 index 0d258cae..00000000 --- a/makedoc.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -mkdir -p ./reports -mkdir -p ./documents/apigen - -if [ -z "$1" ]; then - apigen generate \ - --title 'Onmipay Common API documentation' \ - --source ./src \ - --destination ./documents/apigen - -# -# Left here for further expansion, ignore this for the time being. -# -elif [ "$1" = "common" ]; then - apigen generate \ - --title 'Omnipay Common API documentation' \ - --source ./src/Omnipay/Common \ - --destination ./documents/apigen -fi diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a61160cd..21263b81 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,25 +1,30 @@ - - - - ./tests/ - - - - - - - - ./src - - + stopOnFailure="false"> + + + + + + + + + + src/ + + + + + tests + + + + + diff --git a/phpunit9.xml.dist b/phpunit9.xml.dist new file mode 100644 index 00000000..ab4e9d38 --- /dev/null +++ b/phpunit9.xml.dist @@ -0,0 +1,32 @@ + + + + + src/ + + + + + + + + + + tests + + + + + + diff --git a/src/Omnipay/Common/AbstractGateway.php b/src/Common/AbstractGateway.php similarity index 89% rename from src/Omnipay/Common/AbstractGateway.php rename to src/Common/AbstractGateway.php index 97014a0f..33443360 100755 --- a/src/Omnipay/Common/AbstractGateway.php +++ b/src/Common/AbstractGateway.php @@ -5,8 +5,8 @@ namespace Omnipay\Common; -use Guzzle\Http\ClientInterface; -use Guzzle\Http\Client as HttpClient; +use Omnipay\Common\Http\Client; +use Omnipay\Common\Http\ClientInterface; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request as HttpRequest; @@ -15,7 +15,7 @@ * * This abstract class should be extended by all payment gateways * throughout the Omnipay system. It enforces implementation of - * the GatewayInterface interface and defines various common attibutes + * the GatewayInterface interface and defines various common attributes * and methods that all gateways should have. * * Example: @@ -40,17 +40,16 @@ * * For further code examples see the *omnipay-example* repository on github. * - * @see GatewayInterface */ abstract class AbstractGateway implements GatewayInterface { - /** - * @var \Symfony\Component\HttpFoundation\ParameterBag - */ - protected $parameters; + use ParametersTrait { + setParameter as traitSetParameter; + getParameter as traitGetParameter; + } /** - * @var \Guzzle\Http\ClientInterface + * @var ClientInterface */ protected $httpClient; @@ -62,10 +61,10 @@ abstract class AbstractGateway implements GatewayInterface /** * Create a new gateway instance * - * @param ClientInterface $httpClient A Guzzle client to make API calls with + * @param ClientInterface $httpClient A HTTP client to make API calls with * @param HttpRequest $httpRequest A Symfony HTTP request object */ - public function __construct(ClientInterface $httpClient = null, HttpRequest $httpRequest = null) + public function __construct(?ClientInterface $httpClient = null, ?HttpRequest $httpRequest = null) { $this->httpClient = $httpClient ?: $this->getDefaultHttpClient(); $this->httpRequest = $httpRequest ?: $this->getDefaultHttpRequest(); @@ -114,21 +113,13 @@ public function getDefaultParameters() return array(); } - /** - * @return array - */ - public function getParameters() - { - return $this->parameters->all(); - } - /** * @param string $key * @return mixed */ public function getParameter($key) { - return $this->parameters->get($key); + return $this->traitGetParameter($key); } /** @@ -138,9 +129,7 @@ public function getParameter($key) */ public function setParameter($key, $value) { - $this->parameters->set($key, $value); - - return $this; + return $this->traitSetParameter($key, $value); } /** @@ -227,6 +216,16 @@ public function supportsCompletePurchase() return method_exists($this, 'completePurchase'); } + /** + * Supports Fetch Transaction + * + * @return boolean True if this gateway supports the fetchTransaction() method + */ + public function supportsFetchTransaction() + { + return method_exists($this, 'fetchTransaction'); + } + /** * Supports Refund * @@ -312,7 +311,6 @@ public function supportsUpdateCard() * $myRequest = $gw->myRequest($someParameters); * * - * @see \Omnipay\Common\Message\AbstractRequest * @param string $class The request class name * @param array $parameters * @return \Omnipay\Common\Message\AbstractRequest @@ -327,16 +325,11 @@ protected function createRequest($class, array $parameters) /** * Get the global default HTTP client. * - * @return HttpClient + * @return ClientInterface */ protected function getDefaultHttpClient() { - return new HttpClient( - '', - array( - 'curl.options' => array(CURLOPT_CONNECTTIMEOUT => 60), - ) - ); + return new Client(); } /** diff --git a/src/Omnipay/Common/CreditCard.php b/src/Common/CreditCard.php similarity index 86% rename from src/Omnipay/Common/CreditCard.php rename to src/Common/CreditCard.php index f08f342d..a1f97d05 100644 --- a/src/Omnipay/Common/CreditCard.php +++ b/src/Common/CreditCard.php @@ -93,6 +93,8 @@ */ class CreditCard { + use ParametersTrait; + const BRAND_VISA = 'visa'; const BRAND_MASTERCARD = 'mastercard'; const BRAND_DISCOVER = 'discover'; @@ -133,13 +135,6 @@ class CreditCard self::BRAND_LASER => '/^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/', ); - /** - * Internal storage of all of the card parameters. - * - * @var \Symfony\Component\HttpFoundation\ParameterBag - */ - protected $parameters; - /** * Create a new CreditCard object using the specified parameters * @@ -156,7 +151,6 @@ public function __construct($parameters = null) * Note: The fact that this class knows about a particular card brand does not imply * that your gateway supports it. * - * @see self::$supported_cards * @return array */ public function getSupportedBrands() @@ -194,9 +188,9 @@ public function addSupportedBrand($name, $expression) * If any unknown parameters passed, they will be ignored. * * @param array $parameters An associative array of parameters - * @return CreditCard provides a fluent interface. + * @return $this */ - public function initialize($parameters = null) + public function initialize(?array $parameters = null) { $this->parameters = new ParameterBag; @@ -205,40 +199,6 @@ public function initialize($parameters = null) return $this; } - /** - * Get all parameters. - * - * @return array An associative array of parameters. - */ - public function getParameters() - { - return $this->parameters->all(); - } - - /** - * Get one parameter. - * - * @return mixed A single parameter value. - */ - protected function getParameter($key) - { - return $this->parameters->get($key); - } - - /** - * Set one parameter. - * - * @param string $key Parameter key - * @param mixed $value Parameter value - * @return CreditCard provides a fluent interface. - */ - protected function setParameter($key, $value) - { - $this->parameters->set($key, $value); - - return $this; - } - /** * Set the credit card year. * @@ -246,7 +206,7 @@ protected function setParameter($key, $value) * * @param string $key Parameter key, e.g. 'expiryYear' * @param mixed $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ protected function setYearParameter($key, $value) { @@ -269,14 +229,21 @@ protected function setYearParameter($key, $value) * Generally if you want to validate the credit card yourself with custom error * messages, you should use your framework's validation library, not this method. * - * @throws InvalidCreditCardException * @return void + * @throws Exception\InvalidRequestException + * @throws InvalidCreditCardException */ public function validate() { - foreach (array('number', 'expiryMonth', 'expiryYear') as $key) { + $requiredParameters = array( + 'number' => 'credit card number', + 'expiryMonth' => 'expiration month', + 'expiryYear' => 'expiration year' + ); + + foreach ($requiredParameters as $key => $val) { if (!$this->getParameter($key)) { - throw new InvalidCreditCardException("The $key parameter is required"); + throw new InvalidCreditCardException("The $val is required"); } } @@ -292,7 +259,6 @@ public function validate() throw new InvalidCreditCardException('Card number should have 12 to 19 digits'); } } - /** * Get Card Title. * @@ -307,7 +273,7 @@ public function getTitle() * Set Card Title. * * @param string $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setTitle($value) { @@ -331,7 +297,7 @@ public function getFirstName() * Set Card First Name (Billing and Shipping). * * @param string $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setFirstName($value) { @@ -355,7 +321,7 @@ public function getLastName() * Set Card Last Name (Billing and Shipping). * * @param string $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setLastName($value) { @@ -379,7 +345,7 @@ public function getName() * Set Card Name (Billing and Shipping). * * @param string $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setName($value) { @@ -406,12 +372,12 @@ public function getNumber() * it's safe to pass in strings such as "4444-3333 2222 1111" etc. * * @param string $value Parameter value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setNumber($value) { // strip non-numeric characters - return $this->setParameter('number', preg_replace('/\D/', '', $value)); + return $this->setParameter('number', preg_replace('/\D/', '', (string) $value)); } /** @@ -446,8 +412,10 @@ public function getNumberMasked($mask = 'X') */ public function getBrand() { + $number = (string) $this->getNumber(); + foreach ($this->getSupportedBrands() as $brand => $val) { - if (preg_match($val, $this->getNumber())) { + if (preg_match($val, $number)) { return $brand; } } @@ -467,7 +435,7 @@ public function getExpiryMonth() * Sets the card expiry month. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setExpiryMonth($value) { @@ -488,7 +456,7 @@ public function getExpiryYear() * Sets the card expiry year. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setExpiryYear($value) { @@ -521,7 +489,7 @@ public function getStartMonth() * Sets the card start month. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setStartMonth($value) { @@ -542,7 +510,7 @@ public function getStartYear() * Sets the card start year. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setStartYear($value) { @@ -575,7 +543,7 @@ public function getCvv() * Sets the card CVV. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setCvv($value) { @@ -595,35 +563,36 @@ public function getTracks() /** * Get raw data for track 1 on the credit card magnetic strip. * - * @return string + * @return string|null */ public function getTrack1() { - $track1 = null; - if ($tracks = $this->getTracks()) { - $pattern = '/\%B\d{1,19}\^.{2,26}\^\d{4}\d*\?/'; - if (preg_match($pattern, $tracks, $matches) === 1) { - $track1 = $matches[0]; - } - } - return $track1; + return $this->getTrackByPattern('/\%B\d{1,19}\^.{2,26}\^\d{4}\d*\?/'); } /** * Get raw data for track 2 on the credit card magnetic strip. * - * @return string + * @return string|null */ public function getTrack2() { - $track2 = null; + return $this->getTrackByPattern('/;\d{1,19}=\d{4}\d*\?/'); + } + + /** + * Get raw data for a track on the credit card magnetic strip based on the pattern for track 1 or 2. + * + * @param $pattern + * @return string|null + */ + protected function getTrackByPattern($pattern) + { if ($tracks = $this->getTracks()) { - $pattern = '/;\d{1,19}=\d{4}\d*\?/'; if (preg_match($pattern, $tracks, $matches) === 1) { - $track2 = $matches[0]; + return $matches[0]; } } - return $track2; } /** @@ -631,7 +600,7 @@ public function getTrack2() * transactions. * * @param $value - * @return CreditCard + * @return $this */ public function setTracks($value) { @@ -652,7 +621,7 @@ public function getIssueNumber() * Sets the card issue number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setIssueNumber($value) { @@ -673,7 +642,7 @@ public function getBillingTitle() * Sets the card billing title. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingTitle($value) { @@ -690,17 +659,31 @@ public function getBillingName() return trim($this->getBillingFirstName() . ' ' . $this->getBillingLastName()); } + /** + * Split the full name in the first and last name. + * + * @param $fullName + * @return array with first and lastname + */ + protected function listFirstLastName($fullName) + { + $names = explode(' ', $fullName, 2); + + return [$names[0], isset($names[1]) ? $names[1] : null]; + } + /** * Sets the card billing name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingName($value) { - $names = explode(' ', $value, 2); + $names = $this->listFirstLastName($value); + $this->setBillingFirstName($names[0]); - $this->setBillingLastName(isset($names[1]) ? $names[1] : null); + $this->setBillingLastName($names[1]); return $this; } @@ -719,7 +702,7 @@ public function getBillingFirstName() * Sets the first part of the card billing name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingFirstName($value) { @@ -740,7 +723,7 @@ public function getBillingLastName() * Sets the last part of the card billing name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingLastName($value) { @@ -761,7 +744,7 @@ public function getBillingCompany() * Sets the billing company name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingCompany($value) { @@ -782,7 +765,7 @@ public function getBillingAddress1() * Sets the billing address, line 1. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingAddress1($value) { @@ -803,7 +786,7 @@ public function getBillingAddress2() * Sets the billing address, line 2. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingAddress2($value) { @@ -824,7 +807,7 @@ public function getBillingCity() * Sets billing city. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingCity($value) { @@ -845,7 +828,7 @@ public function getBillingPostcode() * Sets the billing postcode. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingPostcode($value) { @@ -866,7 +849,7 @@ public function getBillingState() * Sets the billing state. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingState($value) { @@ -887,7 +870,7 @@ public function getBillingCountry() * Sets the billing country name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingCountry($value) { @@ -908,7 +891,7 @@ public function getBillingPhone() * Sets the billing phone number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingPhone($value) { @@ -929,7 +912,7 @@ public function getBillingPhoneExtension() * Sets the billing phone number extension. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingPhoneExtension($value) { @@ -950,7 +933,7 @@ public function getBillingFax() * Sets the billing fax number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBillingFax($value) { @@ -971,7 +954,7 @@ public function getShippingTitle() * Sets the title of the card shipping name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingTitle($value) { @@ -992,13 +975,14 @@ public function getShippingName() * Sets the card shipping name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingName($value) { - $names = explode(' ', $value, 2); + $names = $this->listFirstLastName($value); + $this->setShippingFirstName($names[0]); - $this->setShippingLastName(isset($names[1]) ? $names[1] : null); + $this->setShippingLastName($names[1]); return $this; } @@ -1017,7 +1001,7 @@ public function getShippingFirstName() * Sets the first part of the card shipping name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingFirstName($value) { @@ -1038,7 +1022,7 @@ public function getShippingLastName() * Sets the last part of the card shipping name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingLastName($value) { @@ -1059,7 +1043,7 @@ public function getShippingCompany() * Sets the shipping company name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingCompany($value) { @@ -1080,7 +1064,7 @@ public function getShippingAddress1() * Sets the shipping address, line 1. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingAddress1($value) { @@ -1101,7 +1085,7 @@ public function getShippingAddress2() * Sets the shipping address, line 2. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingAddress2($value) { @@ -1122,7 +1106,7 @@ public function getShippingCity() * Sets the shipping city. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingCity($value) { @@ -1143,7 +1127,7 @@ public function getShippingPostcode() * Sets the shipping postcode. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingPostcode($value) { @@ -1164,7 +1148,7 @@ public function getShippingState() * Sets the shipping state. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingState($value) { @@ -1185,7 +1169,7 @@ public function getShippingCountry() * Sets the shipping country. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingCountry($value) { @@ -1206,7 +1190,7 @@ public function getShippingPhone() * Sets the shipping phone number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingPhone($value) { @@ -1227,7 +1211,7 @@ public function getShippingPhoneExtension() * Sets the shipping phone number extension. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingPhoneExtension($value) { @@ -1248,7 +1232,7 @@ public function getShippingFax() * Sets the shipping fax number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setShippingFax($value) { @@ -1269,7 +1253,7 @@ public function getAddress1() * Sets the billing and shipping address, line 1. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setAddress1($value) { @@ -1293,7 +1277,7 @@ public function getAddress2() * Sets the billing and shipping address, line 2. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setAddress2($value) { @@ -1317,7 +1301,7 @@ public function getCity() * Sets the billing and shipping city. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setCity($value) { @@ -1341,7 +1325,7 @@ public function getPostcode() * Sets the billing and shipping postcode. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setPostcode($value) { @@ -1365,7 +1349,7 @@ public function getState() * Sets the billing and shipping state. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setState($value) { @@ -1389,7 +1373,7 @@ public function getCountry() * Sets the billing and shipping country. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setCountry($value) { @@ -1413,7 +1397,7 @@ public function getPhone() * Sets the billing and shipping phone number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setPhone($value) { @@ -1437,7 +1421,7 @@ public function getPhoneExtension() * Sets the billing and shipping phone number extension. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setPhoneExtension($value) { @@ -1461,7 +1445,7 @@ public function getFax() * Sets the billing and shipping fax number. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setFax($value) { @@ -1485,7 +1469,7 @@ public function getCompany() * Sets the billing and shipping company name. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setCompany($value) { @@ -1509,7 +1493,7 @@ public function getEmail() * Sets the cardholder's email address. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setEmail($value) { @@ -1532,7 +1516,7 @@ public function getBirthday($format = 'Y-m-d') * Sets the cardholder's birthday. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setBirthday($value) { @@ -1559,7 +1543,7 @@ public function getGender() * Sets the cardholder's gender. * * @param string $value - * @return CreditCard provides a fluent interface. + * @return $this */ public function setGender($value) { diff --git a/src/Omnipay/Common/Exception/BadMethodCallException.php b/src/Common/Exception/BadMethodCallException.php similarity index 100% rename from src/Omnipay/Common/Exception/BadMethodCallException.php rename to src/Common/Exception/BadMethodCallException.php diff --git a/src/Omnipay/Common/Exception/InvalidCreditCardException.php b/src/Common/Exception/InvalidCreditCardException.php similarity index 100% rename from src/Omnipay/Common/Exception/InvalidCreditCardException.php rename to src/Common/Exception/InvalidCreditCardException.php diff --git a/src/Omnipay/Common/Exception/InvalidRequestException.php b/src/Common/Exception/InvalidRequestException.php similarity index 100% rename from src/Omnipay/Common/Exception/InvalidRequestException.php rename to src/Common/Exception/InvalidRequestException.php diff --git a/src/Omnipay/Common/Exception/InvalidResponseException.php b/src/Common/Exception/InvalidResponseException.php similarity index 100% rename from src/Omnipay/Common/Exception/InvalidResponseException.php rename to src/Common/Exception/InvalidResponseException.php diff --git a/src/Omnipay/Common/Exception/OmnipayException.php b/src/Common/Exception/OmnipayException.php similarity index 100% rename from src/Omnipay/Common/Exception/OmnipayException.php rename to src/Common/Exception/OmnipayException.php diff --git a/src/Omnipay/Common/Exception/RuntimeException.php b/src/Common/Exception/RuntimeException.php similarity index 100% rename from src/Omnipay/Common/Exception/RuntimeException.php rename to src/Common/Exception/RuntimeException.php diff --git a/src/Omnipay/Common/GatewayFactory.php b/src/Common/GatewayFactory.php similarity index 66% rename from src/Omnipay/Common/GatewayFactory.php rename to src/Common/GatewayFactory.php index f81ba667..8d887d59 100644 --- a/src/Omnipay/Common/GatewayFactory.php +++ b/src/Common/GatewayFactory.php @@ -5,8 +5,8 @@ namespace Omnipay\Common; -use Guzzle\Http\ClientInterface; use Omnipay\Common\Exception\RuntimeException; +use Omnipay\Common\Http\ClientInterface; use Symfony\Component\HttpFoundation\Request as HttpRequest; /** @@ -26,7 +26,6 @@ * $gateway = Omnipay::create('ExpressGateway'); * * - * @see Omnipay\Omnipay */ class GatewayFactory { @@ -69,35 +68,16 @@ public function register($className) } } - /** - * Automatically find and register all officially supported gateways - * - * @return array An array of gateway names - */ - public function find() - { - foreach ($this->getSupportedGateways() as $gateway) { - $class = Helper::getGatewayClassName($gateway); - if (class_exists($class)) { - $this->register($gateway); - } - } - - ksort($this->gateways); - - return $this->all(); - } - /** * Create a new gateway instance * * @param string $class Gateway name - * @param ClientInterface|null $httpClient A Guzzle HTTP Client implementation + * @param ClientInterface|null $httpClient A HTTP Client implementation * @param HttpRequest|null $httpRequest A Symfony HTTP Request implementation * @throws RuntimeException If no such gateway is found * @return GatewayInterface An object of class $class is created and returned */ - public function create($class, ClientInterface $httpClient = null, HttpRequest $httpRequest = null) + public function create($class, ?ClientInterface $httpClient = null, ?HttpRequest $httpRequest = null) { $class = Helper::getGatewayClassName($class); @@ -107,16 +87,4 @@ public function create($class, ClientInterface $httpClient = null, HttpRequest $ return new $class($httpClient, $httpRequest); } - - /** - * Get a list of supported gateways which may be available - * - * @return array - */ - public function getSupportedGateways() - { - $package = json_decode(file_get_contents(__DIR__.'/../../../composer.json'), true); - - return $package['extra']['gateways']; - } } diff --git a/src/Omnipay/Common/GatewayInterface.php b/src/Common/GatewayInterface.php similarity index 71% rename from src/Omnipay/Common/GatewayInterface.php rename to src/Common/GatewayInterface.php index 86d57717..34218c96 100644 --- a/src/Omnipay/Common/GatewayInterface.php +++ b/src/Common/GatewayInterface.php @@ -11,35 +11,39 @@ * This interface class defines the standard functions that any * Omnipay gateway needs to define. * - * @see AbstractGateway * - * @method \Omnipay\Common\Message\RequestInterface authorize(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\NotificationInterface acceptNotification(array $options = array()) (Optional method) + * Receive and handle an instant payment notification (IPN) + * @method \Omnipay\Common\Message\RequestInterface authorize(array $options = array()) (Optional method) * Authorize an amount on the customers card - * @method \Omnipay\Common\Message\RequestInterface completeAuthorize(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface completeAuthorize(array $options = array()) (Optional method) * Handle return from off-site gateways after authorization - * @method \Omnipay\Common\Message\RequestInterface capture(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface capture(array $options = array()) (Optional method) * Capture an amount you have previously authorized - * @method \Omnipay\Common\Message\RequestInterface purchase(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface purchase(array $options = array()) (Optional method) * Authorize and immediately capture an amount on the customers card - * @method \Omnipay\Common\Message\RequestInterface completePurchase(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface completePurchase(array $options = array()) (Optional method) * Handle return from off-site gateways after purchase - * @method \Omnipay\Common\Message\RequestInterface refund(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface refund(array $options = array()) (Optional method) * Refund an already processed transaction - * @method \Omnipay\Common\Message\RequestInterface void(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface fetchTransaction(array $options = []) (Optional method) + * Fetches transaction information + * @method \Omnipay\Common\Message\RequestInterface void(array $options = array()) (Optional method) * Generally can only be called up to 24 hours after submitting a transaction - * @method \Omnipay\Common\Message\RequestInterface createCard(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface createCard(array $options = array()) (Optional method) * The returned response object includes a cardReference, which can be used for future transactions - * @method \Omnipay\Common\Message\RequestInterface updateCard(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface updateCard(array $options = array()) (Optional method) * Update a stored card - * @method \Omnipay\Common\Message\RequestInterface deleteCard(array $options = array()) (Optional method) + * @method \Omnipay\Common\Message\RequestInterface deleteCard(array $options = array()) (Optional method) * Delete a stored card -*/ + */ interface GatewayInterface { /** * Get gateway display name * * This can be used by carts to get the display name for each gateway. + * @return string */ public function getName(); @@ -48,6 +52,7 @@ public function getName(); * * This name can be used with GatewayFactory as an alias of the gateway class, * to create new instances of this gateway. + * @return string */ public function getShortName(); @@ -59,17 +64,18 @@ public function getShortName(); * 'testMode' => false, // boolean variable * 'landingPage' => array('billing', 'login'), // enum variable, first item is default * ); + * @return array */ public function getDefaultParameters(); /** * Initialize gateway with parameters + * @return $this */ public function initialize(array $parameters = array()); /** * Get all gateway parameters - * * @return array */ public function getParameters(); diff --git a/src/Omnipay/Common/Helper.php b/src/Common/Helper.php similarity index 76% rename from src/Omnipay/Common/Helper.php rename to src/Common/Helper.php index 13637378..7886aecd 100644 --- a/src/Omnipay/Common/Helper.php +++ b/src/Common/Helper.php @@ -42,6 +42,7 @@ function ($match) { protected static function convertToLowercase($str) { $explodedStr = explode('_', $str); + $lowercasedStr = []; if (count($explodedStr) > 1) { foreach ($explodedStr as $value) { @@ -62,7 +63,7 @@ protected static function convertToLowercase($str) public static function validateLuhn($number) { $str = ''; - foreach (array_reverse(str_split($number)) as $i => $c) { + foreach (array_reverse(str_split((string) $number)) as $i => $c) { $str .= $i % 2 ? $c * 2 : $c; } @@ -78,9 +79,9 @@ public static function validateLuhn($number) * @param mixed $target The object to set parameters on * @param array $parameters An array of parameters to set */ - public static function initialize($target, $parameters) + public static function initialize($target, ?array $parameters = null) { - if (is_array($parameters)) { + if ($parameters) { foreach ($parameters as $key => $value) { $method = 'set'.ucfirst(static::camelCase($key)); if (method_exists($target, $method)) { @@ -121,12 +122,18 @@ public static function getGatewayShortName($className) * PayPal\Express => \Omnipay\PayPal\ExpressGateway * PayPal_Express => \Omnipay\PayPal\ExpressGateway * - * @param string $shortName The short gateway name + * @param string $shortName The short gateway name or the FQCN * @return string The fully namespaced gateway class name */ public static function getGatewayClassName($shortName) { - if (0 === strpos($shortName, '\\')) { + // If the class starts with \ or Omnipay\, assume it's a FQCN + if (0 === strpos($shortName, '\\') || 0 === strpos($shortName, 'Omnipay\\')) { + return $shortName; + } + + // Check if the class exists and implements the Gateway Interface, if so -> FCQN + if (is_subclass_of($shortName, GatewayInterface::class, true)) { return $shortName; } @@ -138,30 +145,4 @@ public static function getGatewayClassName($shortName) return '\\Omnipay\\'.$shortName.'Gateway'; } - - /** - * Convert an amount into a float. - * The float datatype can then be converted into the string - * format that the remote gateway requies. - * - * @var string|int|float $value The value to convert. - * @throws InvalidArgumentException on a validation failure. - * @return float The amount converted to a float. - */ - - public static function toFloat($value) - { - if (!is_string($value) && !is_int($value) && !is_float($value)) { - throw new InvalidArgumentException('Data type is not a valid decimal number.'); - } - - if (is_string($value)) { - // Validate generic number, with optional sign and decimals. - if (!preg_match('/^[-]?[0-9]+(\.[0-9]*)?$/', $value)) { - throw new InvalidArgumentException('String is not a valid decimal number.'); - } - } - - return (float)$value; - } } diff --git a/src/Common/Http/Client.php b/src/Common/Http/Client.php new file mode 100644 index 00000000..7eece585 --- /dev/null +++ b/src/Common/Http/Client.php @@ -0,0 +1,72 @@ +httpClient = $httpClient ?: HttpClientDiscovery::find(); + $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); + } + + /** + * @param $method + * @param $uri + * @param array $headers + * @param string|array|resource|StreamInterface|null $body + * @param string $protocolVersion + * @return ResponseInterface + * @throws \Http\Client\Exception + */ + public function request( + $method, + $uri, + array $headers = [], + $body = null, + $protocolVersion = '1.1' + ) { + $request = $this->requestFactory->createRequest($method, $uri, $headers, $body, $protocolVersion); + + return $this->sendRequest($request); + } + + /** + * @param RequestInterface $request + * @return ResponseInterface + * @throws \Http\Client\Exception + */ + private function sendRequest(RequestInterface $request) + { + try { + return $this->httpClient->sendRequest($request); + } catch (\Http\Client\Exception\NetworkException $networkException) { + throw new NetworkException($networkException->getMessage(), $request, $networkException); + } catch (\Exception $exception) { + throw new RequestException($exception->getMessage(), $request, $exception); + } + } +} diff --git a/src/Common/Http/ClientInterface.php b/src/Common/Http/ClientInterface.php new file mode 100644 index 00000000..73593957 --- /dev/null +++ b/src/Common/Http/ClientInterface.php @@ -0,0 +1,34 @@ +request = $request; + + parent::__construct($message, 0, $previous); + } + + /** + * Returns the request. + * + * The request object MAY be a different object from the one passed to ClientInterface::sendRequest() + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/src/Common/Http/Exception/NetworkException.php b/src/Common/Http/Exception/NetworkException.php new file mode 100644 index 00000000..068133dc --- /dev/null +++ b/src/Common/Http/Exception/NetworkException.php @@ -0,0 +1,9 @@ +initialize($parameters); } @@ -37,7 +33,7 @@ public function __construct($parameters = null) * @param array|null $parameters An array of parameters to set on this object * @return $this Item */ - public function initialize($parameters = null) + public function initialize(?array $parameters = null) { $this->parameters = new ParameterBag; @@ -46,23 +42,6 @@ public function initialize($parameters = null) return $this; } - public function getParameters() - { - return $this->parameters->all(); - } - - protected function getParameter($key) - { - return $this->parameters->get($key); - } - - protected function setParameter($key, $value) - { - $this->parameters->set($key, $value); - - return $this; - } - /** * {@inheritDoc} */ diff --git a/src/Omnipay/Common/ItemBag.php b/src/Common/ItemBag.php similarity index 92% rename from src/Omnipay/Common/ItemBag.php rename to src/Common/ItemBag.php index f726c3b7..7515335b 100644 --- a/src/Omnipay/Common/ItemBag.php +++ b/src/Common/ItemBag.php @@ -11,14 +11,12 @@ * This class defines a bag (multi element set or array) of single cart items * in the Omnipay system. * - * @see Item */ class ItemBag implements \IteratorAggregate, \Countable { /** * Item storage * - * @see Item * * @var array */ @@ -37,7 +35,6 @@ public function __construct(array $items = array()) /** * Return all the items * - * @see Item * * @return array An array of items */ @@ -49,7 +46,6 @@ public function all() /** * Replace the contents of this bag with the specified items * - * @see Item * * @param array $items An array of items */ @@ -65,7 +61,6 @@ public function replace(array $items = array()) /** * Add an item to the bag * - * @see Item * * @param ItemInterface|array $item An existing item, or associative array of item parameters */ @@ -83,7 +78,7 @@ public function add($item) * * @return \ArrayIterator An \ArrayIterator instance */ - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->items); } @@ -93,7 +88,7 @@ public function getIterator() * * @return int The number of items */ - public function count() + public function count(): int { return count($this->items); } diff --git a/src/Omnipay/Common/ItemInterface.php b/src/Common/ItemInterface.php similarity index 100% rename from src/Omnipay/Common/ItemInterface.php rename to src/Common/ItemInterface.php diff --git a/src/Omnipay/Common/Message/AbstractRequest.php b/src/Common/Message/AbstractRequest.php similarity index 72% rename from src/Omnipay/Common/Message/AbstractRequest.php rename to src/Common/Message/AbstractRequest.php index 14d34ce1..004a7757 100644 --- a/src/Omnipay/Common/Message/AbstractRequest.php +++ b/src/Common/Message/AbstractRequest.php @@ -5,16 +5,22 @@ namespace Omnipay\Common\Message; -use Guzzle\Http\ClientInterface; +use Money\Currencies\ISOCurrencies; +use Money\Currency; +use Money\Formatter\DecimalMoneyFormatter; +use Money\Money; +use Money\Number; +use Money\Parser\DecimalMoneyParser; use Omnipay\Common\CreditCard; -use Omnipay\Common\Currency; use Omnipay\Common\Exception\InvalidRequestException; use Omnipay\Common\Exception\RuntimeException; use Omnipay\Common\Helper; +use Omnipay\Common\Http\Client; +use Omnipay\Common\Http\ClientInterface; use Omnipay\Common\ItemBag; +use Omnipay\Common\ParametersTrait; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request as HttpRequest; -use InvalidArgumentException; /** * Abstract Request @@ -56,22 +62,17 @@ * // now do something with the $myResponse object, test for success, etc. * * - * @see RequestInterface - * @see AbstractResponse */ abstract class AbstractRequest implements RequestInterface { - /** - * The request parameters - * - * @var \Symfony\Component\HttpFoundation\ParameterBag - */ - protected $parameters; + use ParametersTrait { + setParameter as traitSetParameter; + } /** * The request client. * - * @var \Guzzle\Http\ClientInterface + * @var ClientInterface */ protected $httpClient; @@ -89,6 +90,11 @@ abstract class AbstractRequest implements RequestInterface */ protected $response; + /** + * @var ISOCurrencies + */ + protected $currencies; + /** * @var bool */ @@ -102,7 +108,7 @@ abstract class AbstractRequest implements RequestInterface /** * Create a new Request * - * @param ClientInterface $httpClient A Guzzle client to make API calls with + * @param ClientInterface $httpClient A HTTP client to make API calls with * @param HttpRequest $httpRequest A Symfony HTTP request object */ public function __construct(ClientInterface $httpClient, HttpRequest $httpRequest) @@ -135,33 +141,12 @@ public function initialize(array $parameters = array()) return $this; } - /** - * Get all parameters as an associative array. - * - * @return array - */ - public function getParameters() - { - return $this->parameters->all(); - } - - /** - * Get a single parameter. - * - * @param string $key The parameter key - * @return mixed - */ - protected function getParameter($key) - { - return $this->parameters->get($key); - } - /** * Set a single parameter * * @param string $key The parameter key * @param mixed $value The value to set - * @return AbstractRequest Provides a fluent interface + * @return $this * @throws RuntimeException if a request parameter is modified after the request has been sent. */ protected function setParameter($key, $value) @@ -170,9 +155,7 @@ protected function setParameter($key, $value) throw new RuntimeException('Request cannot be modified after it has been sent!'); } - $this->parameters->set($key, $value); - - return $this; + return $this->traitSetParameter($key, $value); } /** @@ -189,32 +172,13 @@ public function getTestMode() * Sets the test mode of the request. * * @param boolean $value True for test mode on. - * @return AbstractRequest + * @return $this */ public function setTestMode($value) { return $this->setParameter('testMode', $value); } - /** - * Validate the request. - * - * This method is called internally by gateways to avoid wasting time with an API call - * when the request is clearly invalid. - * - * @param string ... a variable length list of required parameters - * @throws InvalidRequestException - */ - public function validate() - { - foreach (func_get_args() as $key) { - $value = $this->parameters->get($key); - if (! isset($value)) { - throw new InvalidRequestException("The $key parameter is required"); - } - } - } - /** * Get the card. * @@ -229,7 +193,7 @@ public function getCard() * Sets the card. * * @param CreditCard $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setCard($value) { @@ -254,7 +218,7 @@ public function getToken() * Sets the card token. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setToken($value) { @@ -275,7 +239,7 @@ public function getCardReference() * Sets the card reference. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setCardReference($value) { @@ -283,76 +247,89 @@ public function setCardReference($value) } /** - * Convert an amount into a float. - * - * @var string|int|float $value The value to convert. - * @throws InvalidRequestException on any validation failure. - * @return float The amount converted to a float. + * @return ISOCurrencies */ - - public function toFloat($value) + protected function getCurrencies() { - try { - return Helper::toFloat($value); - } catch (InvalidArgumentException $e) { - // Throw old exception for legacy implementations. - throw new InvalidRequestException($e->getMessage(), $e->getCode(), $e); + if ($this->currencies === null) { + $this->currencies = new ISOCurrencies(); } + + return $this->currencies; } /** - * Validates and returns the formated amount. - * - * @throws InvalidRequestException on any validation failure. - * @return string The amount formatted to the correct number of decimal places for the selected currency. + * @param string|int|null $amount + * @return null|Money + * @throws InvalidRequestException */ - public function getAmount() + private function getMoney($amount = null) { - $amount = $this->getParameter('amount'); - - if ($amount !== null) { - // Don't allow integers for currencies that support decimals. - // This is for legacy reasons - upgrades from v0.9 - if ($this->getCurrencyDecimalPlaces() > 0) { - if (is_int($amount) || (is_string($amount) && false === strpos((string) $amount, '.'))) { - throw new InvalidRequestException( - 'Please specify amount as a string or float, ' - . 'with decimal places (e.g. \'10.00\' to represent $10.00).' - ); - }; - } + $currencyCode = $this->getCurrency() ?: 'USD'; + $currency = new Currency($currencyCode); - $amount = $this->toFloat($amount); + $amount = $amount !== null ? $amount : $this->getParameter('amount'); - // Check for a negative amount. - if (!$this->negativeAmountAllowed && $amount < 0) { - throw new InvalidRequestException('A negative amount is not allowed.'); - } + if ($amount === null) { + return null; + } elseif ($amount instanceof Money) { + $money = $amount; + } elseif (is_integer($amount)) { + $money = new Money($amount, $currency); + } else { + $moneyParser = new DecimalMoneyParser($this->getCurrencies()); - // Check for a zero amount. - if (!$this->zeroAmountAllowed && $amount === 0.0) { - throw new InvalidRequestException('A zero amount is not allowed.'); - } + $number = Number::fromString($amount); // Check for rounding that may occur if too many significant decimal digits are supplied. - $decimal_count = strlen(substr(strrchr(sprintf('%.8g', $amount), '.'), 1)); - if ($decimal_count > $this->getCurrencyDecimalPlaces()) { + $decimal_count = strlen($number->getFractionalPart()); + $subunit = $this->getCurrencies()->subunitFor($currency); + if ($decimal_count > $subunit) { throw new InvalidRequestException('Amount precision is too high for currency.'); } - return $this->formatCurrency($amount); + $money = $moneyParser->parse((string) $number, $currency); + } + + // Check for a negative amount. + if (!$this->negativeAmountAllowed && $money->isNegative()) { + throw new InvalidRequestException('A negative amount is not allowed.'); + } + + // Check for a zero amount. + if (!$this->zeroAmountAllowed && $money->isZero()) { + throw new InvalidRequestException('A zero amount is not allowed.'); + } + + return $money; + } + + /** + * Validates and returns the formatted amount. + * + * @throws InvalidRequestException on any validation failure. + * @return string The amount formatted to the correct number of decimal places for the selected currency. + */ + public function getAmount() + { + $money = $this->getMoney(); + + if ($money !== null) { + $moneyFormatter = new DecimalMoneyFormatter($this->getCurrencies()); + + return $moneyFormatter->format($money); } } /** * Sets the payment amount. * - * @param string $value - * @return AbstractRequest Provides a fluent interface + * @param string|null $value + * @return $this */ public function setAmount($value) { - return $this->setParameter('amount', $value); + return $this->setParameter('amount', $value !== null ? (string) $value : null); } /** @@ -362,7 +339,37 @@ public function setAmount($value) */ public function getAmountInteger() { - return (int) round($this->getAmount() * $this->getCurrencyDecimalFactor()); + $money = $this->getMoney(); + + if ($money !== null) { + return (int) $money->getAmount(); + } + } + + /** + * Sets the payment amount as integer. + * + * @param int $value + * @return $this + */ + public function setAmountInteger($value) + { + return $this->setParameter('amount', (int) $value); + } + + /** + * Sets the payment amount as integer. + * + * @param Money $value + * @return $this + */ + public function setMoney(Money $value) + { + $currency = $value->getCurrency()->getCode(); + + $this->setCurrency($currency); + + return $this->setParameter('amount', $value); } /** @@ -379,7 +386,7 @@ public function getCurrency() * Sets the payment currency code. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setCurrency($value) { @@ -392,12 +399,18 @@ public function setCurrency($value) /** * Get the payment currency number. * - * @return integer + * @return string|null */ public function getCurrencyNumeric() { - if ($currency = Currency::find($this->getCurrency())) { - return $currency->getNumeric(); + if (! $this->getCurrency()) { + return null; + } + + $currency = new Currency($this->getCurrency()); + + if ($this->getCurrencies()->contains($currency)) { + return (string) $this->getCurrencies()->numericCodeFor($currency); } } @@ -408,31 +421,28 @@ public function getCurrencyNumeric() */ public function getCurrencyDecimalPlaces() { - if ($currency = Currency::find($this->getCurrency())) { - return $currency->getDecimals(); + if ($this->getCurrency()) { + $currency = new Currency($this->getCurrency()); + if ($this->getCurrencies()->contains($currency)) { + return $this->getCurrencies()->subunitFor($currency); + } } return 2; } - private function getCurrencyDecimalFactor() - { - return pow(10, $this->getCurrencyDecimalPlaces()); - } - /** * Format an amount for the payment currency. * + * @param string $amount * @return string */ public function formatCurrency($amount) { - return number_format( - $amount, - $this->getCurrencyDecimalPlaces(), - '.', - '' - ); + $money = $this->getMoney((string) $amount); + $formatter = new DecimalMoneyFormatter($this->getCurrencies()); + + return $formatter->format($money); } /** @@ -449,7 +459,7 @@ public function getDescription() * Sets the request description. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setDescription($value) { @@ -472,7 +482,7 @@ public function getTransactionId() * Sets the transaction ID. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setTransactionId($value) { @@ -496,7 +506,7 @@ public function getTransactionReference() * Sets the transaction reference. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setTransactionReference($value) { @@ -517,7 +527,7 @@ public function getItems() * Set the items in this order * * @param ItemBag|array $items An array of items in this order - * @return AbstractRequest + * @return $this */ public function setItems($items) { @@ -542,7 +552,7 @@ public function getClientIp() * Sets the client IP address. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setClientIp($value) { @@ -563,7 +573,7 @@ public function getReturnUrl() * Sets the request return URL. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setReturnUrl($value) { @@ -584,7 +594,7 @@ public function getCancelUrl() * Sets the request cancel URL. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setCancelUrl($value) { @@ -605,7 +615,7 @@ public function getNotifyUrl() * Sets the request notify URL. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setNotifyUrl($value) { @@ -632,7 +642,7 @@ public function getIssuer() * the bank where an account is held (separate from the card brand). * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setIssuer($value) { @@ -659,7 +669,7 @@ public function getPaymentMethod() * multiple payment providers with a single API. * * @param string $value - * @return AbstractRequest Provides a fluent interface + * @return $this */ public function setPaymentMethod($value) { diff --git a/src/Omnipay/Common/Message/AbstractResponse.php b/src/Common/Message/AbstractResponse.php similarity index 64% rename from src/Omnipay/Common/Message/AbstractResponse.php rename to src/Common/Message/AbstractResponse.php index 27b63be5..d78f21a7 100644 --- a/src/Omnipay/Common/Message/AbstractResponse.php +++ b/src/Common/Message/AbstractResponse.php @@ -26,7 +26,6 @@ * // now do something with the $myResponse object, test for success, etc. * * - * @see ResponseInterface */ abstract class AbstractResponse implements ResponseInterface { @@ -157,68 +156,111 @@ public function getTransactionId() return null; } + /** + * Gets the redirect target url. + * + * @return string + */ + public function getRedirectUrl() + { + return null; + } + + /** + * Get the required redirect method (either GET or POST). + * + * @return string + */ + public function getRedirectMethod() + { + return 'GET'; + } + + /** + * Gets the redirect form data array, if the redirect method is POST. + * + * @return array + */ + public function getRedirectData() + { + return []; + } + /** * Automatically perform any required redirect * * This method is meant to be a helper for simple scenarios. If you want to customize the * redirection page, just call the getRedirectUrl() and getRedirectData() methods directly. * - * @codeCoverageIgnore - * * @return void */ public function redirect() { $this->getRedirectResponse()->send(); - exit; } /** - * @return HttpRedirectResponse + * @return HttpRedirectResponse|HttpResponse */ public function getRedirectResponse() { - if (!$this instanceof RedirectResponseInterface || !$this->isRedirect()) { - throw new RuntimeException('This response does not support redirection.'); - } + $this->validateRedirect(); if ('GET' === $this->getRedirectMethod()) { - return HttpRedirectResponse::create($this->getRedirectUrl()); - } elseif ('POST' === $this->getRedirectMethod()) { - $hiddenFields = ''; - foreach ($this->getRedirectData() as $key => $value) { - $hiddenFields .= sprintf( - '', - htmlentities($key, ENT_QUOTES, 'UTF-8', false), - htmlentities($value, ENT_QUOTES, 'UTF-8', false) - )."\n"; - } - - $output = ' + return new HttpRedirectResponse($this->getRedirectUrl()); + } + + $hiddenFields = ''; + foreach ($this->getRedirectData() as $key => $value) { + $hiddenFields .= sprintf( + '', + htmlentities($key, ENT_QUOTES, 'UTF-8', false), + htmlentities((string) $value, ENT_QUOTES, 'UTF-8', false) + )."\n"; + } + + $output = ' - - - Redirecting... - - -
-

Redirecting to payment page...

-

- %2$s - -

-
- + + + Redirecting... + + +
+

Redirecting to payment page...

+

+ %2$s + +

+
+ '; - $output = sprintf( - $output, - htmlentities($this->getRedirectUrl(), ENT_QUOTES, 'UTF-8', false), - $hiddenFields - ); + $output = sprintf( + $output, + htmlentities($this->getRedirectUrl(), ENT_QUOTES, 'UTF-8', false), + $hiddenFields + ); + + return new HttpResponse($output); + } - return HttpResponse::create($output); + /** + * Validate that the current Response is a valid redirect. + * + * @return void + */ + protected function validateRedirect() + { + if (!$this instanceof RedirectResponseInterface || !$this->isRedirect()) { + throw new RuntimeException('This response does not support redirection.'); } - throw new RuntimeException('Invalid redirect method "'.$this->getRedirectMethod().'".'); + if (empty($this->getRedirectUrl())) { + throw new RuntimeException('The given redirectUrl cannot be empty.'); + } + + if (!in_array($this->getRedirectMethod(), ['GET', 'POST'])) { + throw new RuntimeException('Invalid redirect method "'.$this->getRedirectMethod().'".'); + } } } diff --git a/src/Omnipay/Common/Message/FetchIssuersResponseInterface.php b/src/Common/Message/FetchIssuersResponseInterface.php similarity index 93% rename from src/Omnipay/Common/Message/FetchIssuersResponseInterface.php rename to src/Common/Message/FetchIssuersResponseInterface.php index e86d6a4a..5b22f425 100644 --- a/src/Omnipay/Common/Message/FetchIssuersResponseInterface.php +++ b/src/Common/Message/FetchIssuersResponseInterface.php @@ -15,8 +15,6 @@ * This happens when the gateway needs the customer to choose a * card issuer. * - * @see ResponseInterface - * @see Omnipay\Common\Issuer */ interface FetchIssuersResponseInterface extends ResponseInterface { diff --git a/src/Omnipay/Common/Message/FetchPaymentMethodsResponseInterface.php b/src/Common/Message/FetchPaymentMethodsResponseInterface.php similarity index 93% rename from src/Omnipay/Common/Message/FetchPaymentMethodsResponseInterface.php rename to src/Common/Message/FetchPaymentMethodsResponseInterface.php index 57ca8800..3aa3e65c 100644 --- a/src/Omnipay/Common/Message/FetchPaymentMethodsResponseInterface.php +++ b/src/Common/Message/FetchPaymentMethodsResponseInterface.php @@ -15,8 +15,6 @@ * This happens when the gateway needs the customer to choose a * payment method. * - * @see ResponseInterface - * @see Omnipay\Common\PaymentMethod */ interface FetchPaymentMethodsResponseInterface extends ResponseInterface { diff --git a/src/Omnipay/Common/Message/MessageInterface.php b/src/Common/Message/MessageInterface.php similarity index 91% rename from src/Omnipay/Common/Message/MessageInterface.php rename to src/Common/Message/MessageInterface.php index f472e2d0..44bb5e1d 100644 --- a/src/Omnipay/Common/Message/MessageInterface.php +++ b/src/Common/Message/MessageInterface.php @@ -9,7 +9,7 @@ * Message Interface * * This interface class defines the standard functions that any Omnipay message - * interface needs to be able to provide. + * interface needs to be able to provide. */ interface MessageInterface { diff --git a/src/Omnipay/Common/Message/NotificationInterface.php b/src/Common/Message/NotificationInterface.php similarity index 77% rename from src/Omnipay/Common/Message/NotificationInterface.php rename to src/Common/Message/NotificationInterface.php index 14d53a90..41193a37 100755 --- a/src/Omnipay/Common/Message/NotificationInterface.php +++ b/src/Common/Message/NotificationInterface.php @@ -21,8 +21,8 @@ public function getTransactionReference(); /** * Was the transaction successful? * - * @return string Transaction status, one of {@see STATUS_COMPLETED}, {@see #STATUS_PENDING}, - * or {@see #STATUS_FAILED}. + * @return string Transaction status, one of {@link NotificationInterface::STATUS_COMPLETED}, + * {@link NotificationInterface::STATUS_PENDING}, or {@link NotificationInterface::STATUS_FAILED}. */ public function getTransactionStatus(); diff --git a/src/Omnipay/Common/Message/RedirectResponseInterface.php b/src/Common/Message/RedirectResponseInterface.php similarity index 97% rename from src/Omnipay/Common/Message/RedirectResponseInterface.php rename to src/Common/Message/RedirectResponseInterface.php index da585646..8ab7374f 100644 --- a/src/Omnipay/Common/Message/RedirectResponseInterface.php +++ b/src/Common/Message/RedirectResponseInterface.php @@ -13,7 +13,6 @@ * interface class with some extra functions relating to the * specifics of a redirect response from the gateway. * - * @see ResponseInterface */ interface RedirectResponseInterface extends ResponseInterface { diff --git a/src/Omnipay/Common/Message/RequestInterface.php b/src/Common/Message/RequestInterface.php similarity index 97% rename from src/Omnipay/Common/Message/RequestInterface.php rename to src/Common/Message/RequestInterface.php index 3a47f823..cdce2ce5 100644 --- a/src/Omnipay/Common/Message/RequestInterface.php +++ b/src/Common/Message/RequestInterface.php @@ -11,7 +11,6 @@ * This interface class defines the standard functions that any Omnipay request * interface needs to be able to provide. It is an extension of MessageInterface. * - * @see MessageInterface */ interface RequestInterface extends MessageInterface { diff --git a/src/Omnipay/Common/Message/ResponseInterface.php b/src/Common/Message/ResponseInterface.php similarity index 98% rename from src/Omnipay/Common/Message/ResponseInterface.php rename to src/Common/Message/ResponseInterface.php index 31081d98..caab8c8c 100644 --- a/src/Omnipay/Common/Message/ResponseInterface.php +++ b/src/Common/Message/ResponseInterface.php @@ -11,7 +11,6 @@ * This interface class defines the standard functions that any Omnipay response * interface needs to be able to provide. It is an extension of MessageInterface. * - * @see MessageInterface */ interface ResponseInterface extends MessageInterface { diff --git a/src/Common/ParametersTrait.php b/src/Common/ParametersTrait.php new file mode 100644 index 00000000..b3c1959a --- /dev/null +++ b/src/Common/ParametersTrait.php @@ -0,0 +1,85 @@ +parameters->set($key, $value); + + return $this; + } + + /** + * Get one parameter. + * + * @param string $key Parameter key + * @return mixed A single parameter value. + */ + protected function getParameter($key) + { + return $this->parameters->get($key); + } + + /** + * Get all parameters. + * + * @return array An associative array of parameters. + */ + public function getParameters() + { + return $this->parameters->all(); + } + + /** + * Initialize the object with parameters. + * + * If any unknown parameters passed, they will be ignored. + * + * @param array $parameters An associative array of parameters + * @return $this. + */ + public function initialize(array $parameters = []) + { + $this->parameters = new ParameterBag; + Helper::initialize($this, $parameters); + return $this; + } + + /** + * Validate the request. + * + * This method is called internally by gateways to avoid wasting time with an API call + * when the request is clearly invalid. + * + * @param string ... a variable length list of required parameters + * @throws InvalidRequestException + */ + public function validate(...$args) + { + foreach ($args as $key) { + $value = $this->parameters->get($key); + if (! isset($value)) { + throw new InvalidRequestException("The $key parameter is required"); + } + } + } +} diff --git a/src/Omnipay/Common/PaymentMethod.php b/src/Common/PaymentMethod.php similarity index 96% rename from src/Omnipay/Common/PaymentMethod.php rename to src/Common/PaymentMethod.php index 5f14b16e..2571eb32 100644 --- a/src/Omnipay/Common/PaymentMethod.php +++ b/src/Common/PaymentMethod.php @@ -10,7 +10,6 @@ * * This class defines a payment method to be used in the Omnipay system. * - * @see Issuer */ class PaymentMethod { @@ -19,8 +18,6 @@ class PaymentMethod * The ID of the payment method. Used as the payment method ID in the * Issuer class. * - * @see Issuer - * * @var string */ protected $id; diff --git a/src/Omnipay/Omnipay.php b/src/Omnipay.php similarity index 85% rename from src/Omnipay/Omnipay.php rename to src/Omnipay.php index fb064e7e..85903958 100644 --- a/src/Omnipay/Omnipay.php +++ b/src/Omnipay.php @@ -6,6 +6,7 @@ namespace Omnipay; use Omnipay\Common\GatewayFactory; +use Omnipay\Common\Http\ClientInterface; /** * Omnipay class @@ -46,10 +47,10 @@ * @method static array find() * @method static array getSupportedGateways() * @codingStandardsIgnoreStart - * @method static \Omnipay\Common\GatewayInterface create(string $class, \Guzzle\Http\ClientInterface $httpClient = null, \Symfony\Component\HttpFoundation\Request $httpRequest = null) + * @method static \Omnipay\Common\GatewayInterface create(string $class, ClientInterface $httpClient = null, \Symfony\Component\HttpFoundation\Request $httpRequest = null) * @codingStandardsIgnoreEnd * - * @see Omnipay\Common\GatewayFactory + * @see \Omnipay\Common\GatewayFactory */ class Omnipay { @@ -70,11 +71,11 @@ class Omnipay */ public static function getFactory() { - if (is_null(static::$factory)) { - static::$factory = new GatewayFactory; + if (is_null(self::$factory)) { + self::$factory = new GatewayFactory; } - return static::$factory; + return self::$factory; } /** @@ -82,9 +83,9 @@ public static function getFactory() * * @param GatewayFactory $factory A GatewayFactory instance */ - public static function setFactory(GatewayFactory $factory = null) + public static function setFactory(?GatewayFactory $factory = null) { - static::$factory = $factory; + self::$factory = $factory; } /** @@ -105,12 +106,12 @@ public static function setFactory(GatewayFactory $factory = null) * * @param string $method The factory method to invoke. * @param array $parameters Parameters passed to the factory method. - * + * * @return mixed */ public static function __callStatic($method, $parameters) { - $factory = static::getFactory(); + $factory = self::getFactory(); return call_user_func_array(array($factory, $method), $parameters); } diff --git a/src/Omnipay/Common/Currency.php b/src/Omnipay/Common/Currency.php deleted file mode 100644 index 7b71cc49..00000000 --- a/src/Omnipay/Common/Currency.php +++ /dev/null @@ -1,243 +0,0 @@ -code = $code; - $this->numeric = $numeric; - $this->decimals = $decimals; - } - - /** - * Get the three letter code for the currency - * - * @return string - */ - public function getCode() - { - return $this->code; - } - - /** - * Get the numeric code for this currency - * - * @return string - */ - public function getNumeric() - { - return $this->numeric; - } - - /** - * Get the number of decimal places for this currency - * - * @return int - */ - public function getDecimals() - { - return $this->decimals; - } - - /** - * Find a specific currency - * - * @param string $code The three letter currency code - * @return mixed A Currency object, or null if no currency was found - */ - public static function find($code) - { - $code = strtoupper($code); - $currencies = static::all(); - - if (isset($currencies[$code])) { - return new static($code, $currencies[$code]['numeric'], $currencies[$code]['decimals']); - } - } - - /** - * Get an array of all supported currencies - * - * @return array - */ - public static function all() - { - return array( - 'AED' => array('numeric' => '784', 'decimals' => 2), - 'AFN' => array('numeric' => '971', 'decimals' => 2), - 'ALL' => array('numeric' => '008', 'decimals' => 2), - 'AMD' => array('numeric' => '051', 'decimals' => 2), - 'ANG' => array('numeric' => '532', 'decimals' => 2), - 'AOA' => array('numeric' => '973', 'decimals' => 2), - 'ARS' => array('numeric' => '032', 'decimals' => 2), - 'AUD' => array('numeric' => '036', 'decimals' => 2), - 'AWG' => array('numeric' => '533', 'decimals' => 2), - 'AZN' => array('numeric' => '944', 'decimals' => 2), - 'BAM' => array('numeric' => '977', 'decimals' => 2), - 'BBD' => array('numeric' => '052', 'decimals' => 2), - 'BDT' => array('numeric' => '050', 'decimals' => 2), - 'BGN' => array('numeric' => '975', 'decimals' => 2), - 'BHD' => array('numeric' => '048', 'decimals' => 3), - 'BIF' => array('numeric' => '108', 'decimals' => 0), - 'BMD' => array('numeric' => '060', 'decimals' => 2), - 'BND' => array('numeric' => '096', 'decimals' => 2), - 'BOB' => array('numeric' => '068', 'decimals' => 2), - 'BRL' => array('numeric' => '986', 'decimals' => 2), - 'BSD' => array('numeric' => '044', 'decimals' => 2), - 'BTC' => array('numeric' => null, 'decimals' => 8), - 'BTN' => array('numeric' => '064', 'decimals' => 2), - 'BWP' => array('numeric' => '072', 'decimals' => 2), - 'BYR' => array('numeric' => '974', 'decimals' => 0), - 'BZD' => array('numeric' => '084', 'decimals' => 2), - 'CAD' => array('numeric' => '124', 'decimals' => 2), - 'CDF' => array('numeric' => '976', 'decimals' => 2), - 'CHF' => array('numeric' => '756', 'decimals' => 2), - 'CLP' => array('numeric' => '152', 'decimals' => 0), - 'CNY' => array('numeric' => '156', 'decimals' => 2), - 'COP' => array('numeric' => '170', 'decimals' => 2), - 'CRC' => array('numeric' => '188', 'decimals' => 2), - 'CUC' => array('numeric' => '931', 'decimals' => 2), - 'CUP' => array('numeric' => '192', 'decimals' => 2), - 'CVE' => array('numeric' => '132', 'decimals' => 2), - 'CZK' => array('numeric' => '203', 'decimals' => 2), - 'DJF' => array('numeric' => '262', 'decimals' => 0), - 'DKK' => array('numeric' => '208', 'decimals' => 2), - 'DOP' => array('numeric' => '214', 'decimals' => 2), - 'DZD' => array('numeric' => '012', 'decimals' => 2), - 'EGP' => array('numeric' => '818', 'decimals' => 2), - 'ERN' => array('numeric' => '232', 'decimals' => 2), - 'ETB' => array('numeric' => '230', 'decimals' => 2), - 'EUR' => array('numeric' => '978', 'decimals' => 2), - 'FJD' => array('numeric' => '242', 'decimals' => 2), - 'FKP' => array('numeric' => '238', 'decimals' => 2), - 'GBP' => array('numeric' => '826', 'decimals' => 2), - 'GEL' => array('numeric' => '981', 'decimals' => 2), - 'GHS' => array('numeric' => '936', 'decimals' => 2), - 'GIP' => array('numeric' => '292', 'decimals' => 2), - 'GMD' => array('numeric' => '270', 'decimals' => 2), - 'GNF' => array('numeric' => '324', 'decimals' => 0), - 'GTQ' => array('numeric' => '320', 'decimals' => 2), - 'GYD' => array('numeric' => '328', 'decimals' => 2), - 'HKD' => array('numeric' => '344', 'decimals' => 2), - 'HNL' => array('numeric' => '340', 'decimals' => 2), - 'HRK' => array('numeric' => '191', 'decimals' => 2), - 'HTG' => array('numeric' => '332', 'decimals' => 2), - 'HUF' => array('numeric' => '348', 'decimals' => 2), - 'IDR' => array('numeric' => '360', 'decimals' => 2), - 'ILS' => array('numeric' => '376', 'decimals' => 2), - 'INR' => array('numeric' => '356', 'decimals' => 2), - 'IQD' => array('numeric' => '368', 'decimals' => 3), - 'IRR' => array('numeric' => '364', 'decimals' => 2), - 'ISK' => array('numeric' => '352', 'decimals' => 0), - 'JMD' => array('numeric' => '388', 'decimals' => 2), - 'JOD' => array('numeric' => '400', 'decimals' => 3), - 'JPY' => array('numeric' => '392', 'decimals' => 0), - 'KES' => array('numeric' => '404', 'decimals' => 2), - 'KGS' => array('numeric' => '417', 'decimals' => 2), - 'KHR' => array('numeric' => '116', 'decimals' => 2), - 'KMF' => array('numeric' => '174', 'decimals' => 0), - 'KPW' => array('numeric' => '408', 'decimals' => 2), - 'KRW' => array('numeric' => '410', 'decimals' => 0), - 'KWD' => array('numeric' => '414', 'decimals' => 3), - 'KYD' => array('numeric' => '136', 'decimals' => 2), - 'KZT' => array('numeric' => '398', 'decimals' => 2), - 'LAK' => array('numeric' => '418', 'decimals' => 0), - 'LBP' => array('numeric' => '422', 'decimals' => 2), - 'LKR' => array('numeric' => '144', 'decimals' => 2), - 'LRD' => array('numeric' => '430', 'decimals' => 2), - 'LSL' => array('numeric' => '426', 'decimals' => 2), - 'LYD' => array('numeric' => '434', 'decimals' => 3), - 'MAD' => array('numeric' => '504', 'decimals' => 2), - 'MDL' => array('numeric' => '498', 'decimals' => 2), - 'MGA' => array('numeric' => '969', 'decimals' => 0), - 'MKD' => array('numeric' => '807', 'decimals' => 2), - 'MMK' => array('numeric' => '104', 'decimals' => 2), - 'MNT' => array('numeric' => '496', 'decimals' => 2), - 'MOP' => array('numeric' => '446', 'decimals' => 2), - 'MRO' => array('numeric' => '478', 'decimals' => 0), - 'MUR' => array('numeric' => '480', 'decimals' => 2), - 'MVR' => array('numeric' => '462', 'decimals' => 2), - 'MWK' => array('numeric' => '454', 'decimals' => 2), - 'MXN' => array('numeric' => '484', 'decimals' => 2), - 'MYR' => array('numeric' => '458', 'decimals' => 2), - 'MZN' => array('numeric' => '943', 'decimals' => 2), - 'NAD' => array('numeric' => '516', 'decimals' => 2), - 'NGN' => array('numeric' => '566', 'decimals' => 2), - 'NIO' => array('numeric' => '558', 'decimals' => 2), - 'NOK' => array('numeric' => '578', 'decimals' => 2), - 'NPR' => array('numeric' => '524', 'decimals' => 2), - 'NZD' => array('numeric' => '554', 'decimals' => 2), - 'OMR' => array('numeric' => '512', 'decimals' => 3), - 'PAB' => array('numeric' => '590', 'decimals' => 2), - 'PEN' => array('numeric' => '604', 'decimals' => 2), - 'PGK' => array('numeric' => '598', 'decimals' => 2), - 'PHP' => array('numeric' => '608', 'decimals' => 2), - 'PKR' => array('numeric' => '586', 'decimals' => 2), - 'PLN' => array('numeric' => '985', 'decimals' => 2), - 'PYG' => array('numeric' => '600', 'decimals' => 0), - 'QAR' => array('numeric' => '634', 'decimals' => 2), - 'RON' => array('numeric' => '946', 'decimals' => 2), - 'RSD' => array('numeric' => '941', 'decimals' => 0), - 'RUB' => array('numeric' => '643', 'decimals' => 2), - 'RWF' => array('numeric' => '646', 'decimals' => 0), - 'SAR' => array('numeric' => '682', 'decimals' => 2), - 'SBD' => array('numeric' => '090', 'decimals' => 2), - 'SCR' => array('numeric' => '690', 'decimals' => 2), - 'SDG' => array('numeric' => '938', 'decimals' => 2), - 'SEK' => array('numeric' => '752', 'decimals' => 2), - 'SGD' => array('numeric' => '702', 'decimals' => 2), - 'SHP' => array('numeric' => '654', 'decimals' => 2), - 'SLL' => array('numeric' => '694', 'decimals' => 2), - 'SOS' => array('numeric' => '706', 'decimals' => 2), - 'SRD' => array('numeric' => '968', 'decimals' => 2), - 'SSP' => array('numeric' => '728', 'decimals' => 2), - 'STD' => array('numeric' => '678', 'decimals' => 2), - 'SYP' => array('numeric' => '760', 'decimals' => 2), - 'SZL' => array('numeric' => '748', 'decimals' => 2), - 'THB' => array('numeric' => '764', 'decimals' => 2), - 'TJS' => array('numeric' => '972', 'decimals' => 2), - 'TMT' => array('numeric' => '934', 'decimals' => 2), - 'TND' => array('numeric' => '788', 'decimals' => 3), - 'TOP' => array('numeric' => '776', 'decimals' => 2), - 'TRY' => array('numeric' => '949', 'decimals' => 2), - 'TTD' => array('numeric' => '780', 'decimals' => 2), - 'TWD' => array('numeric' => '901', 'decimals' => 2), - 'TZS' => array('numeric' => '834', 'decimals' => 2), - 'UAH' => array('numeric' => '980', 'decimals' => 2), - 'UGX' => array('numeric' => '800', 'decimals' => 0), - 'USD' => array('numeric' => '840', 'decimals' => 2), - 'UYU' => array('numeric' => '858', 'decimals' => 2), - 'UZS' => array('numeric' => '860', 'decimals' => 2), - 'VEF' => array('numeric' => '937', 'decimals' => 2), - 'VND' => array('numeric' => '704', 'decimals' => 0), - 'VUV' => array('numeric' => '548', 'decimals' => 0), - 'WST' => array('numeric' => '882', 'decimals' => 2), - 'XAF' => array('numeric' => '950', 'decimals' => 0), - 'XCD' => array('numeric' => '951', 'decimals' => 2), - 'XOF' => array('numeric' => '952', 'decimals' => 0), - 'XPF' => array('numeric' => '953', 'decimals' => 0), - 'YER' => array('numeric' => '886', 'decimals' => 2), - 'ZAR' => array('numeric' => '710', 'decimals' => 2), - 'ZMW' => array('numeric' => '967', 'decimals' => 2), - ); - } -} diff --git a/tests/Omnipay/Common/AbstractGatewayTest.php b/tests/Common/AbstractGatewayTest.php similarity index 84% rename from tests/Omnipay/Common/AbstractGatewayTest.php rename to tests/Common/AbstractGatewayTest.php index 71cb83fc..384e09e8 100755 --- a/tests/Omnipay/Common/AbstractGatewayTest.php +++ b/tests/Common/AbstractGatewayTest.php @@ -3,33 +3,38 @@ namespace Omnipay\Common; use Mockery as m; +use Omnipay\Common\Http\Client; use Omnipay\Common\Message\AbstractRequest; use Omnipay\Tests\TestCase; use Symfony\Component\HttpFoundation\ParameterBag; class AbstractGatewayTest extends TestCase { - public function setUp() + /** @var \Omnipay\Common\AbstractGateway */ + protected $gateway; + + public function setUp() : void { - $this->gateway = m::mock('\Omnipay\Common\AbstractGateway')->makePartial(); + $this->gateway = new AbstractGatewayTest_MockAbstractGateway(); $this->gateway->initialize(); } public function testConstruct() { $this->gateway = new AbstractGatewayTest_MockAbstractGateway; - $this->assertInstanceOf('\Guzzle\Http\Client', $this->gateway->getProtectedHttpClient()); + $this->assertInstanceOf(Client::class, $this->gateway->getProtectedHttpClient()); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $this->gateway->getProtectedHttpRequest()); $this->assertSame(array(), $this->gateway->getParameters()); } public function testGetShortName() { - $this->assertSame('\\'.get_class($this->gateway), $this->gateway->getShortName()); + $this->assertSame('Common_AbstractGatewayTest_MockAbstract', $this->gateway->getShortName()); } public function testInitializeDefaults() { + $this->gateway = m::mock('\Omnipay\Common\AbstractGateway')->makePartial(); $defaults = array( 'currency' => 'AUD', // fixed default type 'username' => array('joe', 'fred'), // enum default type @@ -48,6 +53,7 @@ public function testInitializeDefaults() public function testInitializeParameters() { + $this->gateway = m::mock('\Omnipay\Common\AbstractGateway')->makePartial(); $this->gateway->shouldReceive('getDefaultParameters')->once() ->andReturn(array('currency' => 'AUD')); @@ -71,6 +77,15 @@ public function testGetParameters() $this->assertSame(array('testMode' => true), $this->gateway->getParameters()); } + public function testSetSetParameter() + { + $token = 'foobar'; + + $this->gateway->setParameter('token', $token); + + $this->assertEquals($token, $this->gateway->getParameter('token')); + } + public function testTestMode() { $this->assertSame($this->gateway, $this->gateway->setTestMode(true)); @@ -108,6 +123,11 @@ public function testSupportsCompletePurchase() $this->assertFalse($this->gateway->supportsCompletePurchase()); } + public function testSupportsFetchTransaction() + { + $this->assertFalse($this->gateway->supportsFetchTransaction()); + } + public function testSupportsRefund() { $this->assertFalse($this->gateway->supportsRefund()); diff --git a/tests/Omnipay/Common/CreditCardTest.php b/tests/Common/CreditCardTest.php similarity index 93% rename from tests/Omnipay/Common/CreditCardTest.php rename to tests/Common/CreditCardTest.php index b829369a..bc643e4f 100644 --- a/tests/Omnipay/Common/CreditCardTest.php +++ b/tests/Common/CreditCardTest.php @@ -2,6 +2,7 @@ namespace Omnipay\Common; +use Omnipay\Common\Exception\InvalidRequestException; use Omnipay\Tests\TestCase; class CreditCardTest extends TestCase @@ -9,7 +10,7 @@ class CreditCardTest extends TestCase /** @var CreditCard */ private $card; - public function setUp() + public function setUp() : void { $this->card = new CreditCard; $this->card->setNumber('4111111111111111'); @@ -50,57 +51,55 @@ public function testGetParamters() $this->assertSame(2016, $parameters['expiryYear']); } + /** + * @doesNotPerformAssertions + */ public function testValidateFixture() { $this->card->validate(); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage The number parameter is required - */ public function testValidateNumberRequired() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('The credit card number is required'); + $this->card->setNumber(null); $this->card->validate(); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage The expiryMonth parameter is required - */ public function testValidateExpiryMonthRequired() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('The expiration month is required'); + $this->card->setExpiryMonth(null); $this->card->validate(); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage The expiryYear parameter is required - */ public function testValidateExpiryYearRequired() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('The expiration year is required'); + $this->card->setExpiryYear(null); $this->card->validate(); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage Card has expired - */ public function testValidateExpiryDate() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('Card has expired'); + $this->card->setExpiryYear(gmdate('Y')-1); $this->card->validate(); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage Card number is invalid - */ public function testValidateNumber() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('Card number is invalid'); + $this->card->setNumber('4111111111111110'); $this->card->validate(); } @@ -108,7 +107,7 @@ public function testValidateNumber() public function testGetSupportedBrands() { $brands = $this->card->getSupportedBrands(); - $this->assertInternalType('array', $brands); + $this->assertIsArray($brands); $this->assertArrayHasKey(CreditCard::BRAND_VISA, $brands); } @@ -358,6 +357,13 @@ public function testShouldReturnTrack2() $this->assertEquals(';4242424242424242=15201269999944401?', $actual); } + public function testShouldReturnNoTrack() + { + $this->card->setTracks(null); + $actual = $this->card->getTrack2(); + $this->assertNull($actual); + } + public function testIssueNumber() { $this->card->setIssueNumber('12'); @@ -660,22 +666,20 @@ public function testGender() $this->assertEquals('female', $this->card->getGender()); } - /** - * @expectedException Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage Card number is invalid - */ public function testInvalidLuhn() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('Card number is invalid'); + $this->card->setNumber('43'); $this->card->validate(); } - /** - * @expectedException Omnipay\Common\Exception\InvalidCreditCardException - * @expectedExceptionMessage Card number should have 12 to 19 digits - */ public function testInvalidShortCard() { + $this->expectException(\Omnipay\Common\Exception\InvalidCreditCardException::class); + $this->expectExceptionMessage('Card number should have 12 to 19 digits'); + $this->card->setNumber('4440'); $this->card->validate(); } diff --git a/tests/Omnipay/Common/Exception/BadMethodCallExceptionTest.php b/tests/Common/Exception/BadMethodCallExceptionTest.php similarity index 100% rename from tests/Omnipay/Common/Exception/BadMethodCallExceptionTest.php rename to tests/Common/Exception/BadMethodCallExceptionTest.php diff --git a/tests/Omnipay/Common/Exception/InvalidCreditCardExceptionTest.php b/tests/Common/Exception/InvalidCreditCardExceptionTest.php similarity index 100% rename from tests/Omnipay/Common/Exception/InvalidCreditCardExceptionTest.php rename to tests/Common/Exception/InvalidCreditCardExceptionTest.php diff --git a/tests/Omnipay/Common/Exception/InvalidRequestExceptionTest.php b/tests/Common/Exception/InvalidRequestExceptionTest.php similarity index 100% rename from tests/Omnipay/Common/Exception/InvalidRequestExceptionTest.php rename to tests/Common/Exception/InvalidRequestExceptionTest.php diff --git a/tests/Omnipay/Common/Exception/InvalidResponseExceptionTest.php b/tests/Common/Exception/InvalidResponseExceptionTest.php similarity index 100% rename from tests/Omnipay/Common/Exception/InvalidResponseExceptionTest.php rename to tests/Common/Exception/InvalidResponseExceptionTest.php diff --git a/tests/Omnipay/Common/Exception/RuntimeExceptionTest.php b/tests/Common/Exception/RuntimeExceptionTest.php similarity index 100% rename from tests/Omnipay/Common/Exception/RuntimeExceptionTest.php rename to tests/Common/Exception/RuntimeExceptionTest.php diff --git a/tests/Omnipay/Common/GatewayFactoryTest.php b/tests/Common/GatewayFactoryTest.php similarity index 51% rename from tests/Omnipay/Common/GatewayFactoryTest.php rename to tests/Common/GatewayFactoryTest.php index 22556c86..45cccb43 100644 --- a/tests/Omnipay/Common/GatewayFactoryTest.php +++ b/tests/Common/GatewayFactoryTest.php @@ -7,12 +7,15 @@ class GatewayFactoryTest extends TestCase { - public static function setUpBeforeClass() + /** @var GatewayFactory */ + protected $factory; + + public static function setUpBeforeClass() : void { m::mock('alias:Omnipay\\SpareChange\\TestGateway'); } - public function setUp() + public function setUp() : void { $this->factory = new GatewayFactory; } @@ -41,30 +44,6 @@ public function testRegisterExistingGateway() $this->assertSame(array('Milky', 'Bar'), $this->factory->all()); } - public function testFindRegistersAvailableGateways() - { - $this->factory = m::mock('Omnipay\Common\GatewayFactory[getSupportedGateways]'); - $this->factory->shouldReceive('getSupportedGateways')->once() - ->andReturn(array('SpareChange_Test')); - - $gateways = $this->factory->find(); - - $this->assertContains('SpareChange_Test', $gateways); - $this->assertContains('SpareChange_Test', $this->factory->all()); - } - - public function testFindIgnoresUnavailableGateways() - { - $this->factory = m::mock('Omnipay\Common\GatewayFactory[getSupportedGateways]'); - $this->factory->shouldReceive('getSupportedGateways')->once() - ->andReturn(array('SpareChange_Gone')); - - $gateways = $this->factory->find(); - - $this->assertEmpty($gateways); - $this->assertEmpty($this->factory->all()); - } - public function testCreateShortName() { $gateway = $this->factory->create('SpareChange_Test'); @@ -77,19 +56,11 @@ public function testCreateFullyQualified() $this->assertInstanceOf('\\Omnipay\\SpareChange\\TestGateway', $gateway); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage Class '\Omnipay\Invalid\Gateway' not found - */ public function testCreateInvalid() { - $gateway = $this->factory->create('Invalid'); - } - - public function testGetSupportedGateways() - { - $gateways = $this->factory->getSupportedGateways(); + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage("Class '\Omnipay\Invalid\Gateway' not found"); - $this->assertContains('Stripe', $gateways); + $gateway = $this->factory->create('Invalid'); } } diff --git a/tests/Omnipay/Common/HelperTest.php b/tests/Common/HelperTest.php similarity index 63% rename from tests/Omnipay/Common/HelperTest.php rename to tests/Common/HelperTest.php index 003d1571..a2e2d012 100644 --- a/tests/Omnipay/Common/HelperTest.php +++ b/tests/Common/HelperTest.php @@ -43,18 +43,18 @@ public function testValidateLuhnNull() $this->assertTrue($result); } + /** + * @doesNotPerformAssertions + */ public function testInitializeIgnoresNull() { $target = m::mock(); Helper::initialize($target, null); } - public function testInitializeIgnoresString() - { - $target = m::mock(); - Helper::initialize($target, 'invalid'); - } - + /** + * @doesNotPerformAssertions + */ public function testInitializeCallsSetters() { $target = m::mock('\Omnipay\Common\CreditCard'); @@ -64,6 +64,9 @@ public function testInitializeCallsSetters() Helper::initialize($target, array('name' => 'adrian', 'number' => '1234')); } + /** + * @doesNotPerformAssertions + */ public function testInitializeIgnoresInvalidParameters() { $target = m::mock('\Omnipay\Common\CreditCard'); @@ -141,92 +144,9 @@ public function testGetGatewayClassNameUnderscoreNamespace() $this->assertEquals('\\Omnipay\\PayPal\\ExpressGateway', $class); } - /** - * Some valid toFloat() inputs. - */ - public function testToFloatFromFloat() - { - $shortName = Helper::toFloat(1.99); - $this->assertSame(1.99, $shortName); - } - - public function testToFloatFromInt() - { - $shortName = Helper::toFloat(199); - $this->assertSame(199.0, $shortName); - } - - public function testToFloatFromStringDecimal() - { - $shortName = Helper::toFloat("1.99"); - $this->assertSame(1.99, $shortName); - } - - public function testToFloatFromStringRedunantZeroes() - { - $shortName = Helper::toFloat("000009.99900000000"); - $this->assertSame(9.999, $shortName); - } - - public function testToFloatFromStringEmptyDecimal() - { - $shortName = Helper::toFloat("1."); - $this->assertSame(1.0, $shortName); - } - - public function testToFloatFromStringInt() - { - $shortName = Helper::toFloat("199"); - $this->assertSame(199.0, $shortName); - } - - public function testToFloatFromStringIntNegative() - { - $shortName = Helper::toFloat("-199"); - $this->assertSame(-199.0, $shortName); - } - - /** - * Some invalid toFloat() inputs. - */ - - /** - * The number MUST always start with a digit. - * This is arguably an arbitrary rule that perhaps does not need - * to be enforced. - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage String is not a valid decimal number - */ - public function testToFloatFromStringEmptyIntegerPart() - { - $shortName = Helper::toFloat(".99"); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage String is not a valid decimal number - */ - public function testToFloatFromStringTwoDecimalPoints() - { - $shortName = Helper::toFloat("1.99."); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage String is not a valid decimal number - */ - public function testToFloatFromStringWrongDecimalPoints() - { - $shortName = Helper::toFloat("1,99"); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Data type is not a valid decimal number - */ - public function testToFloatFromBoolean() + public function testGetGatewayClassNameFQCN() { - $shortName = Helper::toFloat(false); + $class = Helper::getGatewayClassName('Omnipay\Stripe\PaymentIntentsGateway'); + $this->assertEquals('Omnipay\Stripe\PaymentIntentsGateway', $class); } } diff --git a/tests/Common/Http/ClientTest.php b/tests/Common/Http/ClientTest.php new file mode 100644 index 00000000..b2534824 --- /dev/null +++ b/tests/Common/Http/ClientTest.php @@ -0,0 +1,133 @@ +shouldReceive('createRequest')->withArgs([ + 'GET', + '/path', + [], + null, + '1.1', + ])->andReturn($request); + + $mockClient->shouldReceive('sendRequest') + ->with($request) + ->andReturn($response) + ->once(); + + $this->assertSame($response, $client->request('GET', '/path')); + + } + + public function testSendException() + { + $mockClient = m::mock(HttpClient::class); + $mockFactory = m::mock(RequestFactory::class); + $client = new Client($mockClient, $mockFactory); + + $request = new Request('GET', '/path'); + $response = new Response(); + + $mockFactory->shouldReceive('createRequest')->withArgs([ + 'GET', + '/path', + [], + null, + '1.1', + ])->andReturn($request); + + $mockClient->shouldReceive('sendRequest') + ->with($request) + ->andThrow(new \Exception('Something went wrong')); + + $this->expectException(\Omnipay\Common\Http\Exception\RequestException::class); + $this->expectExceptionMessage('Something went wrong'); + + $client->request('GET', '/path'); + } + + public function testSendNetworkException() + { + $mockClient = m::mock(HttpClient::class); + $mockFactory = m::mock(RequestFactory::class); + $client = new Client($mockClient, $mockFactory); + + $request = new Request('GET', '/path'); + $response = new Response(); + + $mockFactory->shouldReceive('createRequest')->withArgs([ + 'GET', + '/path', + [], + null, + '1.1', + ])->andReturn($request); + + $mockClient->shouldReceive('sendRequest') + ->with($request) + ->andThrow(new NetworkException('Something went wrong', $request)); + + $this->expectException(\Omnipay\Common\Http\Exception\NetworkException::class); + $this->expectExceptionMessage('Something went wrong'); + + $client->request('GET', '/path'); + } + + public function testSendExceptionGetRequest() + { + $mockClient = m::mock(HttpClient::class); + $mockFactory = m::mock(RequestFactory::class); + $client = new Client($mockClient, $mockFactory); + + $request = new Request('GET', '/path'); + $response = new Response(); + + $mockFactory->shouldReceive('createRequest')->withArgs([ + 'GET', + '/path', + [], + null, + '1.1', + ])->andReturn($request); + + $exception = new \Exception('Something went wrong'); + + $mockClient->shouldReceive('sendRequest') + ->with($request) + ->andThrow($exception); + + $this->expectException(\Omnipay\Common\Http\Exception\RequestException::class); + $this->expectExceptionMessage('Something went wrong'); + + + try { + $client->request('GET', '/path'); + } catch (RequestException $e) { + $this->assertSame($request, $e->getRequest()); + $this->assertSame($exception, $e->getPrevious()); + + throw $e; + } + } +} \ No newline at end of file diff --git a/tests/Common/Http/ExceptionTest.php b/tests/Common/Http/ExceptionTest.php new file mode 100644 index 00000000..527bd544 --- /dev/null +++ b/tests/Common/Http/ExceptionTest.php @@ -0,0 +1,22 @@ +assertSame($request, $exception->getRequest()); + $this->assertSame('Something went wrong', $exception->getMessage()); + $this->assertSame($previous, $exception->getPrevious()); + } +} \ No newline at end of file diff --git a/tests/Omnipay/Common/IssuerTest.php b/tests/Common/IssuerTest.php similarity index 100% rename from tests/Omnipay/Common/IssuerTest.php rename to tests/Common/IssuerTest.php diff --git a/tests/Omnipay/Common/ItemBagTest.php b/tests/Common/ItemBagTest.php similarity index 95% rename from tests/Omnipay/Common/ItemBagTest.php rename to tests/Common/ItemBagTest.php index ac865e0c..460690ee 100644 --- a/tests/Omnipay/Common/ItemBagTest.php +++ b/tests/Common/ItemBagTest.php @@ -6,7 +6,10 @@ class ItemBagTest extends TestCase { - public function setUp() + /** @var ItemBag */ + protected $bag; + + public function setUp() : void { $this->bag = new ItemBag; } diff --git a/tests/Omnipay/Common/ItemTest.php b/tests/Common/ItemTest.php similarity index 94% rename from tests/Omnipay/Common/ItemTest.php rename to tests/Common/ItemTest.php index 8c008b51..c653e646 100644 --- a/tests/Omnipay/Common/ItemTest.php +++ b/tests/Common/ItemTest.php @@ -6,7 +6,10 @@ class ItemTest extends TestCase { - public function setUp() + /** @var Item */ + protected $item; + + public function setUp() : void { $this->item = new Item; } diff --git a/tests/Omnipay/Common/Message/AbstractRequestTest.php b/tests/Common/Message/AbstractRequestTest.php similarity index 80% rename from tests/Omnipay/Common/Message/AbstractRequestTest.php rename to tests/Common/Message/AbstractRequestTest.php index 0e07a984..8f196e34 100644 --- a/tests/Omnipay/Common/Message/AbstractRequestTest.php +++ b/tests/Common/Message/AbstractRequestTest.php @@ -3,15 +3,20 @@ namespace Omnipay\Common\Message; use Mockery as m; +use Money\Currency; +use Money\Money; use Omnipay\Common\CreditCard; use Omnipay\Common\ItemBag; use Omnipay\Tests\TestCase; class AbstractRequestTest extends TestCase { - public function setUp() + /** @var AbstractRequest */ + protected $request; + + public function setUp(): void { - $this->request = m::mock('\Omnipay\Common\Message\AbstractRequest')->makePartial(); + $this->request = new AbstractRequestTest_MockAbstractRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->initialize(); } @@ -43,12 +48,12 @@ public function testInitializeWithParams() $this->assertSame('1.23', $this->request->getAmount()); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage Request cannot be modified after it has been sent! - */ public function testInitializeAfterRequestSent() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage("Request cannot be modified after it has been sent!"); + + $this->request = new AbstractRequestTest_MockAbstractRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->send(); @@ -115,12 +120,11 @@ public function testAmountZeroString() $this->assertSame('0.00', $this->request->getAmount()); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - * @expectedExceptionMessage A zero amount is not allowed. - */ public function testAmountZeroNotAllowed() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + $this->expectExceptionMessage('A zero amount is not allowed.'); + $this->changeProtectedProperty('zeroAmountAllowed', false); $this->request->setAmount('0.00'); $this->request->getAmount(); @@ -149,14 +153,24 @@ public function testAmountPrecision() $this->assertSame('0.01', $this->request->getAmount()); } + // See https://github.com/thephpleague/omnipay-common/issues/143 + public function testAmountPrecisionLargeNumbers() + { + $this->request->setCurrency('VND'); + + $this->assertSame($this->request, $this->request->setAmount('103500000')); + $this->assertSame('103500000', $this->request->getAmount()); + } + /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException * * We still want to catch obvious fractions of the minor units that are * not precision errors at a much lower level. */ public function testAmountPrecisionTooHigh() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + $this->assertSame($this->request, $this->request->setAmount('123.005')); $this->assertSame('123.005', $this->request->getAmount()); } @@ -168,42 +182,33 @@ public function testGetAmountNoDecimals() $this->assertSame('1366', $this->request->getAmount()); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ public function testGetAmountNoDecimalsRounding() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + // There will not be any rounding; the amount is sent as requested or not at all. $this->assertSame($this->request, $this->request->setAmount('136.5')); $this->assertSame($this->request, $this->request->setCurrency('JPY')); $this->request->getAmount(); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ - public function testAmountWithIntThrowsException() + public function testGetAmountInteger() { - // ambiguous value, avoid errors upgrading from v0.9 - $this->assertSame($this->request, $this->request->setAmount(10)); - $this->request->getAmount(); + $this->assertSame($this->request, $this->request->setAmount('13.66')); + $this->assertSame(1366, $this->request->getAmountInteger()); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ - public function testAmountWithIntStringThrowsException() + public function testGetAmountIntegerNull() { - // ambiguous value, avoid errors upgrading from v0.9 - // Some currencies only take integers, so an integer (in a string) should be valid. - $this->assertSame($this->request, $this->request->setAmount('10')); - $this->request->getAmount(); + $this->assertSame($this->request, $this->request->setAmount(null)); + $this->assertSame(null, $this->request->getAmountInteger()); } - public function testGetAmountInteger() + public function testSetAmountInteger() { - $this->assertSame($this->request, $this->request->setAmount('13.66')); + $this->assertSame($this->request, $this->request->setAmountInteger(1366)); $this->assertSame(1366, $this->request->getAmountInteger()); + $this->assertSame('13.66', $this->request->getAmount()); } public function testGetAmountIntegerNoDecimals() @@ -213,51 +218,53 @@ public function testGetAmountIntegerNoDecimals() $this->assertSame(1366, $this->request->getAmountInteger()); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ public function testAmountThousandsSepThrowsException() { + $this->expectException(\InvalidArgumentException::class); + $this->assertSame($this->request, $this->request->setAmount('1,234.00')); $this->request->getAmount(); } /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException + * @expectedException \InvalidArgumentException */ public function testAmountInvalidFormatThrowsException() { - $this->assertSame($this->request, $this->request->setAmount('1.234.00')); - $this->request->getAmount(); - } + $this->expectException(\InvalidArgumentException::class); - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ - public function testAmountInvalidTypeThrowsException() - { - $this->assertSame($this->request, $this->request->setAmount(true)); + $this->assertSame($this->request, $this->request->setAmount('1.234.00')); $this->request->getAmount(); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ public function testAmountNegativeStringThrowsException() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + $this->assertSame($this->request, $this->request->setAmount('-123.00')); $this->request->getAmount(); } - /** - * @expectedException Omnipay\Common\Exception\InvalidRequestException - */ public function testAmountNegativeFloatThrowsException() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + $this->assertSame($this->request, $this->request->setAmount(-123.00)); $this->request->getAmount(); } + public function testMoney() + { + $money = new Money(12345, new Currency('EUR')); + $this->assertSame($this->request, $this->request->setMoney($money)); + + $this->request->validate('amount', 'currency'); + + $this->assertSame('123.45', $this->request->getAmount()); + $this->assertSame(12345, $this->request->getAmountInteger()); + $this->assertSame('EUR', $this->request->getCurrency()); + } + public function testCurrency() { $this->assertSame($this->request, $this->request->setCurrency('USD')); @@ -282,17 +289,33 @@ public function testCurrencyNumeric() $this->assertSame('840', $this->request->getCurrencyNumeric()); } + public function testCurrencyNumericNull() + { + $this->assertSame($this->request, $this->request->setCurrency(null)); + $this->assertSame(null, $this->request->getCurrencyNumeric()); + } + + public function testCurrencyNumericUnkown() + { + $this->assertSame($this->request, $this->request->setCurrency('UNKNOWN')); + $this->assertSame(null, $this->request->getCurrencyNumeric()); + } + public function testCurrencyDecimals() { $this->assertSame($this->request, $this->request->setCurrency('JPY')); $this->assertSame(0, $this->request->getCurrencyDecimalPlaces()); } + public function testCurrencyDecimalsDefault() + { + $this->assertSame(2, $this->request->getCurrencyDecimalPlaces()); + } + public function testFormatCurrency() { $this->assertSame('1234.00', $this->request->formatCurrency(1234)); } - public function testFormatCurrencyNoDecimals() { $this->request->setCurrency('JPY'); @@ -398,12 +421,11 @@ public function testGetParameters() $this->assertEquals($expected, $this->request->getParameters()); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage Request cannot be modified after it has been sent! - */ public function testSetParameterAfterRequestSent() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('Request cannot be modified after it has been sent!'); + $this->request = new AbstractRequestTest_MockAbstractRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->send(); @@ -418,11 +440,17 @@ public function testCanValidateExistingParameters() $this->assertNull($this->request->validate('testMode', 'token')); } - /** - * @expectedException \Omnipay\Common\Exception\InvalidRequestException - */ + public function testCanValidateAmountInteger() + { + $this->request->setAmountInteger(1); + + $this->assertNull($this->request->validate('amount')); + } + public function testInvalidParametersThrowsException() { + $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class); + $this->request->setTestMode(true); $this->request->validate('testMode', 'token'); @@ -435,6 +463,7 @@ public function testNoCurrencyReturnedIfCurrencyNotSet() public function testSend() { + $this->request = m::mock('\Omnipay\Common\Message\AbstractRequest')->makePartial(); $response = m::mock('\Omnipay\Common\Message\ResponseInterface'); $data = array('request data'); @@ -444,12 +473,11 @@ public function testSend() $this->assertSame($response, $this->request->send()); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage You must call send() before accessing the Response! - */ public function testGetResponseBeforeRequestSent() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('You must call send() before accessing the Response!'); + $this->request = new AbstractRequestTest_MockAbstractRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->getResponse(); } diff --git a/tests/Omnipay/Common/Message/AbstractResponseTest.php b/tests/Common/Message/AbstractResponseTest.php similarity index 63% rename from tests/Omnipay/Common/Message/AbstractResponseTest.php rename to tests/Common/Message/AbstractResponseTest.php index 1657073d..58b30656 100644 --- a/tests/Omnipay/Common/Message/AbstractResponseTest.php +++ b/tests/Common/Message/AbstractResponseTest.php @@ -7,7 +7,10 @@ class AbstractResponseTest extends TestCase { - public function setUp() + /** @var AbstractResponse */ + protected $response; + + public function setUp(): void { $this->response = m::mock('\Omnipay\Common\Message\AbstractResponse')->makePartial(); } @@ -32,29 +35,56 @@ public function testDefaultMethods() $this->assertNull($this->response->getTransactionReference()); $this->assertNull($this->response->getMessage()); $this->assertNull($this->response->getCode()); + $this->assertNull($this->response->getRedirectUrl()); + $this->assertEquals('GET', $this->response->getRedirectMethod()); + $this->assertEquals([], $this->response->getRedirectData()); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage This response does not support redirection. - */ public function testGetRedirectResponseNotImplemented() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('This response does not support redirection.'); + $this->response->getRedirectResponse(); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage This response does not support redirection. - */ public function testGetRedirectResponseNotSupported() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('This response does not support redirection.'); + $this->response = m::mock('\Omnipay\Common\Message\AbstractResponseTest_MockRedirectResponse')->makePartial(); $this->response->shouldReceive('isRedirect')->once()->andReturn(false); $this->response->getRedirectResponse(); } + public function testGetRedirectResponseUrlNotEmpty() + { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('The given redirectUrl cannot be empty.'); + + $this->response = m::mock('\Omnipay\Common\Message\AbstractResponseTest_MockRedirectResponse')->makePartial(); + $this->response->shouldReceive('getRedirectUrl')->once()->andReturn(null); + + $this->response->getRedirectResponse(); + } + + /** + * @runInSeparateProcess + */ + public function testRedirect() + { + $this->response = m::mock('\Omnipay\Common\Message\AbstractResponseTest_MockRedirectResponse')->makePartial(); + $this->response->shouldReceive('getRedirectMethod')->andReturn('GET'); + + ob_start(); + $this->response->redirect(); + $body = ob_get_clean(); + + $this->assertStringContainsString('Redirecting to https://example.com/redirect?a=1&b=2', $body); + } + public function testGetRedirectResponseGet() { $this->response = m::mock('\Omnipay\Common\Message\AbstractResponseTest_MockRedirectResponse')->makePartial(); @@ -74,17 +104,17 @@ public function testGetRedirectResponsePost() $httpResponse = $this->response->getRedirectResponse(); $this->assertSame(200, $httpResponse->getStatusCode()); - $this->assertContains('
', $httpResponse->getContent()); - $this->assertContains('', $httpResponse->getContent()); - $this->assertContains('', $httpResponse->getContent()); + $this->assertStringContainsString('', $httpResponse->getContent()); + $this->assertStringContainsString('', $httpResponse->getContent()); + $this->assertStringContainsString('', $httpResponse->getContent()); } - /** - * @expectedException \Omnipay\Common\Exception\RuntimeException - * @expectedExceptionMessage Invalid redirect method "DELETE". - */ public function testGetRedirectResponseInvalidMethod() { + $this->expectException(\Omnipay\Common\Exception\RuntimeException::class); + $this->expectExceptionMessage('Invalid redirect method "DELETE".'); + + $this->response = m::mock('\Omnipay\Common\Message\AbstractResponseTest_MockRedirectResponse')->makePartial(); $this->response->shouldReceive('getRedirectMethod')->andReturn('DELETE'); diff --git a/tests/Omnipay/Common/PaymentMethodTest.php b/tests/Common/PaymentMethodTest.php similarity index 100% rename from tests/Omnipay/Common/PaymentMethodTest.php rename to tests/Common/PaymentMethodTest.php diff --git a/tests/Omnipay/Common/CurrencyTest.php b/tests/Omnipay/Common/CurrencyTest.php deleted file mode 100644 index 6eadeffc..00000000 --- a/tests/Omnipay/Common/CurrencyTest.php +++ /dev/null @@ -1,42 +0,0 @@ -assertSame('USD', $currency->getCode()); - $this->assertSame('840', $currency->getNumeric()); - $this->assertSame(2, $currency->getDecimals()); - } - - public function testFindLowercase() - { - $currency = Currency::find('usd'); - - $this->assertSame('USD', $currency->getCode()); - $this->assertSame('840', $currency->getNumeric()); - $this->assertSame(2, $currency->getDecimals()); - } - - public function testUnknownCurrencyReturnsNull() - { - $currency = Currency::find('XYZ'); - - $this->assertNull($currency); - } - - public function testAll() - { - $currencies = Currency::all(); - - $this->assertTrue(isset($currencies['USD'])); - $this->assertFalse(isset($currencies['XYZ'])); - } -} diff --git a/tests/Omnipay/OmnipayTest.php b/tests/OmnipayTest.php similarity index 93% rename from tests/Omnipay/OmnipayTest.php rename to tests/OmnipayTest.php index 2ad08124..9b8167c1 100644 --- a/tests/Omnipay/OmnipayTest.php +++ b/tests/OmnipayTest.php @@ -7,9 +7,11 @@ class OmnipayTest extends TestCase { - public function tearDown() + public function tearDown() : void { Omnipay::setFactory(null); + + parent::tearDown(); } public function testGetFactory() diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 847298ef..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ -add('Omnipay', __DIR__);