Merge trunk (WIP)

This commit is contained in:
dinamiko 2021-10-07 17:36:29 +02:00
commit e36d77fad2
25 changed files with 1052 additions and 195 deletions

314
composer.lock generated
View file

@ -35,20 +35,24 @@
],
"description": "Promoting container interoperability through standard service providers",
"homepage": "https://github.com/container-interop/service-provider",
"support": {
"issues": "https://github.com/container-interop/service-provider/issues",
"source": "https://github.com/container-interop/service-provider/tree/master"
},
"time": "2017-09-20T14:13:36+00:00"
},
{
"name": "dhii/collections-interface",
"version": "v0.3.0-alpha4",
"version": "v0.3.0",
"source": {
"type": "git",
"url": "https://github.com/Dhii/collections-interface.git",
"reference": "da334f75f6477ef7eecaf28df1d5253fe05684ee"
"reference": "74464a969b340d16889eacd9eadc9817f7e7f47a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Dhii/collections-interface/zipball/da334f75f6477ef7eecaf28df1d5253fe05684ee",
"reference": "da334f75f6477ef7eecaf28df1d5253fe05684ee",
"url": "https://api.github.com/repos/Dhii/collections-interface/zipball/74464a969b340d16889eacd9eadc9817f7e7f47a",
"reference": "74464a969b340d16889eacd9eadc9817f7e7f47a",
"shasum": ""
},
"require": {
@ -58,7 +62,7 @@
"require-dev": {
"phpunit/phpunit": "^7.0 | ^8.0 | ^9.0",
"slevomat/coding-standard": "^6.0",
"vimeo/psalm": "^3.11.7 | ^4.0"
"vimeo/psalm": "^4.0"
},
"type": "library",
"extra": {
@ -86,7 +90,7 @@
}
],
"description": "A highly ISP-compliant collection of interfaces that represent maps and lists.",
"time": "2021-03-09T17:36:34+00:00"
"time": "2021-10-06T10:56:09+00:00"
},
{
"name": "dhii/containers",
@ -139,6 +143,10 @@
"PSR-11",
"container"
],
"support": {
"issues": "https://github.com/Dhii/containers/issues",
"source": "https://github.com/Dhii/containers/tree/v0.1.0-alpha1"
},
"time": "2020-09-14T18:27:47+00:00"
},
{
@ -193,6 +201,10 @@
}
],
"description": "Interfaces for working with data containers",
"support": {
"issues": "https://github.com/Dhii/data-container-interface/issues",
"source": "https://github.com/Dhii/data-container-interface/tree/v0.2.1-alpha1"
},
"abandoned": "dhii/collections-interface",
"time": "2019-05-10T14:17:29+00:00"
},
@ -242,6 +254,10 @@
}
],
"description": "Interfaces for most common exceptions",
"support": {
"issues": "https://github.com/Dhii/exception-interface/issues",
"source": "https://github.com/Dhii/exception-interface/tree/develop"
},
"time": "2018-08-29T10:42:04+00:00"
},
{
@ -292,6 +308,10 @@
}
],
"description": "Interfaces for working with factories.",
"support": {
"issues": "https://github.com/Dhii/factory-interface/issues",
"source": "https://github.com/Dhii/factory-interface/tree/master"
},
"abandoned": true,
"time": "2018-08-29T11:15:09+00:00"
},
@ -341,6 +361,10 @@
}
],
"description": "Interfaces for modules",
"support": {
"issues": "https://github.com/Dhii/module-interface/issues",
"source": "https://github.com/Dhii/module-interface/tree/v0.3.0-alpha2"
},
"time": "2021-08-23T08:23:01+00:00"
},
{
@ -383,6 +407,10 @@
}
],
"description": "Interoperability interface for objects that can be cast to string",
"support": {
"issues": "https://github.com/Dhii/stringable-interface/issues",
"source": "https://github.com/Dhii/stringable-interface/tree/master"
},
"abandoned": "symfony/polyfill-php80",
"time": "2017-01-23T15:08:20+00:00"
},
@ -439,6 +467,10 @@
"di",
"wordpress"
],
"support": {
"issues": "https://github.com/Dhii/wp-containers/issues",
"source": "https://github.com/Dhii/wp-containers/tree/v0.1.0-alpha1"
},
"abandoned": "wp-oop/containers",
"time": "2019-05-10T15:04:22+00:00"
},
@ -489,6 +521,10 @@
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/master"
},
"time": "2017-02-14T16:28:37+00:00"
},
{
@ -536,6 +572,9 @@
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.4"
},
"time": "2021-05-03T11:20:27+00:00"
},
{
@ -576,6 +615,10 @@
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
}
],
@ -622,6 +665,10 @@
"runkit",
"testing"
],
"support": {
"issues": "https://github.com/antecedent/patchwork/issues",
"source": "https://github.com/antecedent/patchwork/tree/2.1.15"
},
"time": "2021-08-22T08:00:13+00:00"
},
{
@ -688,6 +735,10 @@
"test",
"testing"
],
"support": {
"issues": "https://github.com/Brain-WP/BrainMonkey/issues",
"source": "https://github.com/Brain-WP/BrainMonkey"
},
"time": "2020-10-13T17:56:14+00:00"
},
{
@ -754,6 +805,10 @@
"stylecheck",
"tests"
],
"support": {
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
},
"time": "2020-12-07T18:04:37+00:00"
},
{
@ -805,6 +860,24 @@
"constructor",
"instantiate"
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/1.4.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-11-10T18:47:58+00:00"
},
{
@ -852,6 +925,10 @@
"keywords": [
"test"
],
"support": {
"issues": "https://github.com/hamcrest/hamcrest-php/issues",
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1"
},
"time": "2020-07-09T08:09:16+00:00"
},
{
@ -965,6 +1042,16 @@
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-11-13T09:40:50+00:00"
},
{
@ -1020,6 +1107,10 @@
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/master"
},
"time": "2018-07-08T19:23:20+00:00"
},
{
@ -1067,6 +1158,10 @@
}
],
"description": "Library for handling version information and constraints",
"support": {
"issues": "https://github.com/phar-io/version/issues",
"source": "https://github.com/phar-io/version/tree/master"
},
"time": "2018-07-08T19:19:57+00:00"
},
{
@ -1125,6 +1220,10 @@
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
},
"time": "2019-12-27T09:44:58+00:00"
},
{
@ -1177,6 +1276,10 @@
"polyfill",
"standards"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
},
"time": "2021-02-15T10:24:51+00:00"
},
{
@ -1227,6 +1330,10 @@
"standards",
"wordpress"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
},
"time": "2021-07-21T11:09:57+00:00"
},
{
@ -1276,6 +1383,10 @@
"reflection",
"static analysis"
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
},
"time": "2020-04-27T09:25:28+00:00"
},
{
@ -1328,6 +1439,10 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x"
},
"time": "2019-12-28T18:55:12+00:00"
},
{
@ -1375,6 +1490,10 @@
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/0.7.2"
},
"time": "2019-08-22T18:11:29+00:00"
},
{
@ -1438,6 +1557,10 @@
"spy",
"stub"
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
},
"time": "2020-03-05T15:02:03+00:00"
},
{
@ -1501,6 +1624,10 @@
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master"
},
"time": "2018-10-31T16:06:48+00:00"
},
{
@ -1551,6 +1678,16 @@
"filesystem",
"iterator"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2021-07-19T06:46:01+00:00"
},
{
@ -1592,6 +1729,10 @@
"keywords": [
"template"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
},
"time": "2015-06-21T13:50:34+00:00"
},
{
@ -1641,6 +1782,16 @@
"keywords": [
"timer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
"source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:20:02+00:00"
},
{
@ -1690,6 +1841,16 @@
"keywords": [
"tokenizer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
"source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"abandoned": true,
"time": "2021-07-26T12:15:06+00:00"
},
@ -1775,6 +1936,10 @@
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20"
},
"time": "2020-01-08T08:45:45+00:00"
},
{
@ -1820,6 +1985,16 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:15:22+00:00"
},
{
@ -1884,6 +2059,16 @@
"compare",
"equality"
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:04:30+00:00"
},
{
@ -1940,6 +2125,16 @@
"unidiff",
"unified diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:59:04+00:00"
},
{
@ -1993,6 +2188,16 @@
"environment",
"hhvm"
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:53:42+00:00"
},
{
@ -2060,6 +2265,16 @@
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:47:53+00:00"
},
{
@ -2111,6 +2326,10 @@
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
},
"time": "2017-04-27T15:39:26+00:00"
},
{
@ -2158,6 +2377,16 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:40:27+00:00"
},
{
@ -2203,6 +2432,16 @@
],
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
"source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:37:18+00:00"
},
{
@ -2256,6 +2495,16 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:34:24+00:00"
},
{
@ -2298,6 +2547,16 @@
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:30:19+00:00"
},
{
@ -2341,6 +2600,10 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
"source": "https://github.com/sebastianbergmann/version/tree/master"
},
"time": "2016-10-03T07:35:21+00:00"
},
{
@ -2392,6 +2655,11 @@
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
"time": "2021-04-09T00:54:41+00:00"
},
{
@ -2454,6 +2722,23 @@
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-02-19T12:13:01+00:00"
},
{
@ -2494,6 +2779,10 @@
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/master"
},
"time": "2019-06-13T22:48:21+00:00"
},
{
@ -2543,6 +2832,10 @@
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.9.1"
},
"time": "2020-07-08T17:02:28+00:00"
},
{
@ -2583,6 +2876,10 @@
"woocommerce",
"wordpress"
],
"support": {
"issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/0.1.1"
},
"time": "2021-07-29T17:25:16+00:00"
},
{
@ -2629,6 +2926,11 @@
"standards",
"wordpress"
],
"support": {
"issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
},
"time": "2020-05-13T23:57:56+00:00"
}
],

View file

@ -141,12 +141,14 @@ return array(
},
'api.endpoint.payments' => static function ( $container ): PaymentsEndpoint {
$authorizations_factory = $container->get( 'api.factory.authorization' );
$capture_factory = $container->get( 'api.factory.capture' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new PaymentsEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$authorizations_factory,
$capture_factory,
$logger
);
},

View file

@ -11,6 +11,8 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
@ -349,8 +351,8 @@ class OrderEndpoint {
$order = $this->order_factory->from_paypal_response( $json );
$purchase_units_payments_captures_status = $order->purchase_units()[0]->payments()->captures()[0]->status() ?? '';
if ( $purchase_units_payments_captures_status && 'DECLINED' === $purchase_units_payments_captures_status ) {
$capture_status = $order->purchase_units()[0]->payments()->captures()[0]->status() ?? null;
if ( $capture_status && $capture_status->is( CaptureStatus::DECLINED ) ) {
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
}
@ -423,6 +425,12 @@ class OrderEndpoint {
throw $error;
}
$order = $this->order_factory->from_paypal_response( $json );
$authorization_status = $order->purchase_units()[0]->payments()->authorizations()[0]->status() ?? null;
if ( $authorization_status && $authorization_status->is( AuthorizationStatus::DENIED ) ) {
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
}
return $order;
}

View file

@ -11,11 +11,13 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
/**
* Class PaymentsEndpoint
@ -45,6 +47,13 @@ class PaymentsEndpoint {
*/
private $authorizations_factory;
/**
* The capture factory.
*
* @var CaptureFactory
*/
private $capture_factory;
/**
* The logger.
*
@ -58,18 +67,21 @@ class PaymentsEndpoint {
* @param string $host The host.
* @param Bearer $bearer The bearer.
* @param AuthorizationFactory $authorization_factory The authorization factory.
* @param CaptureFactory $capture_factory The capture factory.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
string $host,
Bearer $bearer,
AuthorizationFactory $authorization_factory,
CaptureFactory $capture_factory,
LoggerInterface $logger
) {
$this->host = $host;
$this->bearer = $bearer;
$this->authorizations_factory = $authorization_factory;
$this->capture_factory = $capture_factory;
$this->logger = $logger;
}
@ -136,11 +148,11 @@ class PaymentsEndpoint {
*
* @param string $authorization_id The id.
*
* @return Authorization
* @return Capture
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function capture( string $authorization_id ): Authorization {
public function capture( string $authorization_id ): Capture {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/authorizations/' . $authorization_id . '/capture';
$args = array(
@ -167,7 +179,7 @@ class PaymentsEndpoint {
);
}
return $this->authorizations_factory->from_paypal_response( $json );
return $this->capture_factory->from_paypal_response( $json );
}
/**

View file

@ -44,13 +44,21 @@ class AuthorizationStatus {
*/
private $status;
/**
* The details.
*
* @var AuthorizationStatusDetails|null
*/
private $details;
/**
* AuthorizationStatus constructor.
*
* @param string $status The status.
* @param AuthorizationStatusDetails|null $details The details.
* @throws RuntimeException When the status is not valid.
*/
public function __construct( string $status ) {
public function __construct( string $status, ?AuthorizationStatusDetails $details = null ) {
if ( ! in_array( $status, self::VALID_STATUS, true ) ) {
throw new RuntimeException(
sprintf(
@ -61,6 +69,7 @@ class AuthorizationStatus {
);
}
$this->status = $status;
$this->details = $details;
}
/**
@ -91,4 +100,13 @@ class AuthorizationStatus {
public function name(): string {
return $this->status;
}
/**
* Returns the details.
*
* @return AuthorizationStatusDetails|null
*/
public function details(): ?AuthorizationStatusDetails {
return $this->details;
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* The AuthorizationStatusDetails object.
*
* @see https://developer.paypal.com/docs/api/payments/v2/#definition-authorization_status_details
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
/**
* Class AuthorizationStatusDetails
*/
class AuthorizationStatusDetails {
const PENDING_REVIEW = 'PENDING_REVIEW';
/**
* The reason.
*
* @var string
*/
private $reason;
/**
* AuthorizationStatusDetails constructor.
*
* @param string $reason The reason explaining authorization status.
*/
public function __construct( string $reason ) {
$this->reason = $reason;
}
/**
* Compares the current reason with a given one.
*
* @param string $reason The reason to compare with.
*
* @return bool
*/
public function is( string $reason ): bool {
return $this->reason === $reason;
}
/**
* Returns the reason explaining authorization status.
*
* @return string
*/
public function reason(): string {
return $this->reason;
}
}

View file

@ -26,17 +26,10 @@ class Capture {
/**
* The status.
*
* @var string
* @var CaptureStatus
*/
private $status;
/**
* The status details.
*
* @var string
*/
private $status_details;
/**
* The amount.
*
@ -76,8 +69,7 @@ class Capture {
* Capture constructor.
*
* @param string $id The ID.
* @param string $status The status.
* @param string $status_details The status details.
* @param CaptureStatus $status The status.
* @param Amount $amount The amount.
* @param bool $final_capture The final capture.
* @param string $seller_protection The seller protection.
@ -86,8 +78,7 @@ class Capture {
*/
public function __construct(
string $id,
string $status,
string $status_details,
CaptureStatus $status,
Amount $amount,
bool $final_capture,
string $seller_protection,
@ -97,7 +88,6 @@ class Capture {
$this->id = $id;
$this->status = $status;
$this->status_details = $status_details;
$this->amount = $amount;
$this->final_capture = $final_capture;
$this->seller_protection = $seller_protection;
@ -117,21 +107,12 @@ class Capture {
/**
* Returns the status.
*
* @return string
* @return CaptureStatus
*/
public function status() : string {
public function status() : CaptureStatus {
return $this->status;
}
/**
* Returns the status details object.
*
* @return \stdClass
*/
public function status_details() : \stdClass {
return (object) array( 'reason' => $this->status_details );
}
/**
* Returns the amount.
*
@ -183,15 +164,18 @@ class Capture {
* @return array
*/
public function to_array() : array {
return array(
$data = array(
'id' => $this->id(),
'status' => $this->status(),
'status_details' => (array) $this->status_details(),
'status' => $this->status()->name(),
'amount' => $this->amount()->to_array(),
'final_capture' => $this->final_capture(),
'seller_protection' => (array) $this->seller_protection(),
'invoice_id' => $this->invoice_id(),
'custom_id' => $this->custom_id(),
);
if ( $this->status()->details() ) {
$data['status_details'] = array( 'reason' => $this->status()->details()->reason() );
}
return $data;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* The CaptureStatus object.
*
* @see https://developer.paypal.com/docs/api/orders/v2/#definition-capture_status
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
/**
* Class CaptureStatus
*/
class CaptureStatus {
const COMPLETED = 'COMPLETED';
const DECLINED = 'DECLINED';
const PARTIALLY_REFUNDED = 'PARTIALLY_REFUNDED';
const REFUNDED = 'REFUNDED';
const FAILED = 'FAILED';
const PENDING = 'PENDING';
/**
* The status.
*
* @var string
*/
private $status;
/**
* The details.
*
* @var CaptureStatusDetails|null
*/
private $details;
/**
* CaptureStatus constructor.
*
* @param string $status The status.
* @param CaptureStatusDetails|null $details The details.
*/
public function __construct( string $status, ?CaptureStatusDetails $details = null ) {
$this->status = $status;
$this->details = $details;
}
/**
* Compares the current status with a given one.
*
* @param string $status The status to compare with.
*
* @return bool
*/
public function is( string $status ): bool {
return $this->status === $status;
}
/**
* Returns the status.
*
* @return string
*/
public function name(): string {
return $this->status;
}
/**
* Returns the details.
*
* @return CaptureStatusDetails|null
*/
public function details(): ?CaptureStatusDetails {
return $this->details;
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* The CaptureStatusDetails object.
*
* @see https://developer.paypal.com/docs/api/payments/v2/#definition-capture_status_details
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
/**
* Class CaptureStatusDetails
*/
class CaptureStatusDetails {
const BUYER_COMPLAINT = 'BUYER_COMPLAINT';
const CHARGEBACK = 'CHARGEBACK';
const ECHECK = 'ECHECK';
const INTERNATIONAL_WITHDRAWAL = 'INTERNATIONAL_WITHDRAWAL';
const OTHER = 'OTHER';
const PENDING_REVIEW = 'PENDING_REVIEW';
const RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION = 'RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION';
const REFUNDED = 'REFUNDED';
const TRANSACTION_APPROVED_AWAITING_FUNDING = 'TRANSACTION_APPROVED_AWAITING_FUNDING';
const UNILATERAL = 'REFUNDED';
const VERIFICATION_REQUIRED = 'VERIFICATION_REQUIRED';
/**
* The reason.
*
* @var string
*/
private $reason;
/**
* CaptureStatusDetails constructor.
*
* @param string $reason The reason explaining capture status.
*/
public function __construct( string $reason ) {
$this->reason = $reason;
}
/**
* Compares the current reason with a given one.
*
* @param string $reason The reason to compare with.
*
* @return bool
*/
public function is( string $reason ): bool {
return $this->reason === $reason;
}
/**
* Returns the reason explaining capture status.
*
* @return string
*/
public function reason(): string {
return $this->reason;
}
}

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatusDetails;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
@ -39,9 +40,14 @@ class AuthorizationFactory {
);
}
$reason = $data->status_details->reason ?? null;
return new Authorization(
$data->id,
new AuthorizationStatus( $data->status )
new AuthorizationStatus(
$data->status,
$reason ? new AuthorizationStatusDetails( $reason ) : null
)
);
}
}

View file

@ -10,6 +10,8 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatusDetails;
/**
* Class CaptureFactory
@ -42,11 +44,14 @@ class CaptureFactory {
*/
public function from_paypal_response( \stdClass $data ) : Capture {
$reason = isset( $data->status_details->reason ) ? (string) $data->status_details->reason : '';
$reason = $data->status_details->reason ?? null;
return new Capture(
(string) $data->id,
new CaptureStatus(
(string) $data->status,
$reason,
$reason ? new CaptureStatusDetails( $reason ) : null
),
$this->amount_factory->from_paypal_response( $data->amount ),
(bool) $data->final_capture,
(string) $data->seller_protection->status,

View file

@ -57,6 +57,7 @@ return array(
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$environment = $container->get( 'onboarding.environment' );
return new PayPalGateway(
$settings_renderer,
$order_processor,
@ -69,6 +70,7 @@ return array(
$transaction_url_provider,
$subscription_helper,
$page_id,
$environment,
$payment_token_repository,
$logger,
$payments_endpoint,
@ -93,6 +95,8 @@ return array(
$subscription_helper = $container->get( 'subscription.helper' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$environment = $container->get( 'onboarding.environment' );
return new CreditCardGateway(
$settings_renderer,
$order_processor,
@ -110,7 +114,9 @@ return array(
$order_endpoint,
$subscription_helper,
$logger,
$payments_endpoint
$payments_endpoint,
$logger,
$environment
);
},
'wcgateway.disabler' => static function ( $container ): DisableGateways {
@ -219,7 +225,7 @@ return array(
$authorized_payments_processor,
$settings,
$logger,
$environment->current_environment_is( Environment::SANDBOX )
$environment
);
},
'wcgateway.processor.refunds' => static function ( $container ): RefundProcessor {

View file

@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
@ -104,6 +105,13 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
*/
private $order_endpoint;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/**
* CreditCardGateway constructor.
*
@ -124,6 +132,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param LoggerInterface $logger The logger.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param Environment $environment The environment.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@ -142,7 +151,8 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
OrderEndpoint $order_endpoint,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger,
PaymentsEndpoint $payments_endpoint
PaymentsEndpoint $payments_endpoint,
Environment $environment
) {
$this->id = self::ID;
@ -153,6 +163,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
$this->config = $config;
$this->session_handler = $session_handler;
$this->refund_processor = $refund_processor;
$this->environment = $environment;
if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' );
@ -435,4 +446,13 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
private function is_enabled(): bool {
return $this->config->has( 'dcc_enabled' ) && $this->config->get( 'dcc_enabled' );
}
/**
* Returns the environment.
*
* @return Environment
*/
protected function environment(): Environment {
return $this->environment;
}
}

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
@ -20,7 +21,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhooksStatusPage;
@ -143,6 +143,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $order_endpoint;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/**
* PayPalGateway constructor.
*
@ -157,6 +164,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
@ -174,6 +182,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
TransactionUrlProvider $transaction_url_provider,
SubscriptionHelper $subscription_helper,
string $page_id,
Environment $environment,
PaymentTokenRepository $payment_token_repository,
LoggerInterface $logger,
PaymentsEndpoint $payments_endpoint,
@ -190,6 +199,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->page_id = $page_id;
$this->environment = $environment;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
if ( $this->onboarded ) {
@ -286,16 +296,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
$result_status = $this->authorized_payments->process( $wc_order );
$this->render_authorization_message_for_status( $result_status );
if ( AuthorizedPaymentsProcessor::SUCCESSFUL === $result_status ) {
$wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
);
$wc_order->update_meta_data( self::CAPTURED_META_KEY, 'true' );
$wc_order->save();
$wc_order->payment_complete();
return true;
}
if ( AuthorizedPaymentsProcessor::ALREADY_CAPTURED === $result_status ) {
if ( $wc_order->get_status() === 'on-hold' ) {
$wc_order->add_order_note(
@ -308,6 +308,23 @@ class PayPalGateway extends \WC_Payment_Gateway {
$wc_order->payment_complete();
return true;
}
$captures = $this->authorized_payments->captures();
if ( empty( $captures ) ) {
return false;
}
$this->handle_capture_status( end( $captures ), $wc_order );
if ( AuthorizedPaymentsProcessor::SUCCESSFUL === $result_status ) {
$wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
);
$wc_order->update_meta_data( self::CAPTURED_META_KEY, 'true' );
$wc_order->save();
return true;
}
return false;
}
@ -474,4 +491,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
return $ret;
}
/**
* Returns the environment.
*
* @return Environment
*/
protected function environment(): Environment {
return $this->environment;
}
}

View file

@ -10,16 +10,20 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
/**
* Trait ProcessPaymentTrait
*/
trait ProcessPaymentTrait {
use OrderMetaTrait, PaymentsStatusHandlingTrait;
/**
* Process a payment for an WooCommerce order.
*
@ -78,41 +82,37 @@ trait ProcessPaymentTrait {
$selected_token
);
if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'CAPTURE' ) {
$wc_order->update_status(
'processing',
__( 'Payment received.', 'woocommerce-paypal-payments' )
);
$this->add_paypal_meta( $wc_order, $order, $this->environment() );
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
}
if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'AUTHORIZE' ) {
$this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
);
$this->session_handler->destroy_session_data();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
if ( ! $order->status()->is( OrderStatus::COMPLETED ) ) {
$this->logger->warning( "Unexpected status for order {$order->id()} using a saved credit card: " . $order->status()->name() );
return null;
}
if ( ! in_array(
$order->intent(),
array( 'CAPTURE', 'AUTHORIZE' ),
true
) ) {
$this->logger->warning( "Could neither capture nor authorize order {$order->id()} using a saved credit card:" . 'Status: ' . $order->status()->name() . ' Intent: ' . $order->intent() );
return null;
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
}
$this->handle_new_order_status( $order, $wc_order );
} catch ( RuntimeException $error ) {
$this->logger->error( $error->getMessage() );
$this->session_handler->destroy_session_data();
wc_add_notice( $error->getMessage(), 'error' );
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $wc_order ),
);
} catch ( RuntimeException $error ) {
$this->handle_failure( $wc_order, $error );
return null;
}
}
@ -267,12 +267,7 @@ trait ProcessPaymentTrait {
$this->session_handler->destroy_session_data();
} catch ( RuntimeException $error ) {
$wc_order->update_status(
'failed',
__( 'Could not process order.', 'woocommerce-paypal-payments' )
);
$this->session_handler->destroy_session_data();
wc_add_notice( $error->getMessage(), 'error' );
$this->handle_failure( $wc_order, $error );
return $failure_data;
}
@ -316,6 +311,32 @@ trait ProcessPaymentTrait {
return false;
}
/**
* Handles the payment failure.
*
* @param \WC_Order $wc_order The order.
* @param Exception $error The error causing the failure.
*/
protected function handle_failure( \WC_Order $wc_order, Exception $error ): void {
$this->logger->error( 'Payment failed: ' . $error->getMessage() );
$wc_order->update_status(
'failed',
__( 'Could not process order.', 'woocommerce-paypal-payments' )
);
$this->session_handler->destroy_session_data();
wc_add_notice( $error->getMessage(), 'error' );
}
/**
* Returns the environment.
*
* @return Environment
*/
abstract protected function environment(): Environment;
/**
* Checks whether the authorization can be voided.
*

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -23,6 +24,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
*/
class AuthorizedPaymentsProcessor {
use PaymentsStatusHandlingTrait;
const SUCCESSFUL = 'SUCCESSFUL';
const ALREADY_CAPTURED = 'ALREADY_CAPTURED';
const FAILED = 'FAILED';
@ -51,6 +54,13 @@ class AuthorizedPaymentsProcessor {
*/
private $logger;
/**
* The capture results.
*
* @var Capture[]
*/
private $captures;
/**
* AuthorizedPaymentsProcessor constructor.
*
@ -77,6 +87,8 @@ class AuthorizedPaymentsProcessor {
* @return string One of the AuthorizedPaymentsProcessor status constants.
*/
public function process( \WC_Order $wc_order ): string {
$this->captures = array();
try {
$order = $this->paypal_order_from_wc_order( $wc_order );
} catch ( Exception $exception ) {
@ -106,6 +118,15 @@ class AuthorizedPaymentsProcessor {
return self::SUCCESSFUL;
}
/**
* Returns the capture results.
*
* @return Capture[]
*/
public function captures(): array {
return $this->captures;
}
/**
* Returns the PayPal order from a given WooCommerce order.
*
@ -144,7 +165,7 @@ class AuthorizedPaymentsProcessor {
private function capture_authorizations( Authorization ...$authorizations ) {
$uncaptured_authorizations = $this->authorizations_to_capture( ...$authorizations );
foreach ( $uncaptured_authorizations as $authorization ) {
$this->payments_endpoint->capture( $authorization->id() );
$this->captures[] = $this->payments_endpoint->capture( $authorization->id() );
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* Adds common metadata to the order.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Processor
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Trait OrderMetaTrait.
*/
trait OrderMetaTrait {
/**
* Adds common metadata to the order.
*
* @param WC_Order $wc_order The WC order to which metadata will be added.
* @param Order $order The PayPal order.
* @param Environment $environment The environment.
*/
protected function add_paypal_meta(
WC_Order $wc_order,
Order $order,
Environment $environment
): void {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$wc_order->update_meta_data(
PayPalGateway::ORDER_PAYMENT_MODE_META_KEY,
$environment->current_environment_is( Environment::SANDBOX ) ? 'sandbox' : 'live'
);
}
}

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -25,12 +26,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/
class OrderProcessor {
use OrderMetaTrait, PaymentsStatusHandlingTrait;
/**
* Whether current payment mode is sandbox.
* The environment.
*
* @var bool
* @var Environment
*/
protected $sandbox_mode;
protected $environment;
/**
* The payment token repository.
@ -105,7 +108,7 @@ class OrderProcessor {
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
* @param Settings $settings The Settings.
* @param LoggerInterface $logger A logger service.
* @param bool $sandbox_mode Whether sandbox mode enabled.
* @param Environment $environment The environment.
*/
public function __construct(
SessionHandler $session_handler,
@ -115,7 +118,7 @@ class OrderProcessor {
AuthorizedPaymentsProcessor $authorized_payments_processor,
Settings $settings,
LoggerInterface $logger,
bool $sandbox_mode
Environment $environment
) {
$this->session_handler = $session_handler;
@ -124,7 +127,7 @@ class OrderProcessor {
$this->threed_secure = $three_d_secure;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings = $settings;
$this->sandbox_mode = $sandbox_mode;
$this->environment = $environment;
$this->logger = $logger;
}
@ -140,12 +143,8 @@ class OrderProcessor {
if ( ! $order ) {
return false;
}
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$wc_order->update_meta_data(
PayPalGateway::ORDER_PAYMENT_MODE_META_KEY,
$this->sandbox_mode ? 'sandbox' : 'live'
);
$this->add_paypal_meta( $wc_order, $order, $this->environment );
$error_message = null;
if ( ! $this->order_is_approved( $order ) ) {
@ -164,12 +163,14 @@ class OrderProcessor {
}
$order = $this->patch_order( $wc_order, $order );
if ( $order->intent() === 'CAPTURE' ) {
$order = $this->order_endpoint->capture( $order );
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
}
@ -179,14 +180,7 @@ class OrderProcessor {
$this->set_order_transaction_id( $transaction_id, $wc_order );
}
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
);
if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'CAPTURE' ) {
$wc_order->payment_complete();
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->capture_authorized_downloads( $order ) && AuthorizedPaymentsProcessor::SUCCESSFUL === $this->authorized_payments_processor->process( $wc_order ) ) {
$wc_order->add_order_note(

View file

@ -0,0 +1,141 @@
<?php
/**
* Common operations performed after payment authorization/capture.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Processor
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Trait PaymentsStatusHandlingTrait.
*/
trait PaymentsStatusHandlingTrait {
/**
* Changes status of a newly created order, based on the capture/authorization.
*
* @param Order $order The PayPal order.
* @param WC_Order $wc_order The WC order.
*
* @throws RuntimeException If payment denied.
*/
protected function handle_new_order_status(
Order $order,
WC_Order $wc_order
): void {
if ( $order->intent() === 'CAPTURE' ) {
$this->handle_capture_status( $order->purchase_units()[0]->payments()->captures()[0], $wc_order );
} elseif ( $order->intent() === 'AUTHORIZE' ) {
$this->handle_authorization_status( $order->purchase_units()[0]->payments()->authorizations()[0], $wc_order );
}
}
/**
* Changes the order status, based on the capture.
*
* @param Capture $capture The capture.
* @param WC_Order $wc_order The WC order.
*
* @throws RuntimeException If payment denied.
*/
protected function handle_capture_status(
Capture $capture,
WC_Order $wc_order
): void {
$status = $capture->status();
if ( $status->details() ) {
$this->add_status_details_note( $wc_order, $status->name(), $status->details()->reason() );
}
switch ( $status->name() ) {
case CaptureStatus::COMPLETED:
$wc_order->payment_complete();
break;
// It is checked in the capture endpoint already, but there are other ways to capture,
// such as when paid via saved card.
case CaptureStatus::DECLINED:
$wc_order->update_status(
'failed',
__( 'Could not capture the payment.', 'woocommerce-paypal-payments' )
);
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
case CaptureStatus::PENDING:
case CaptureStatus::FAILED:
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
);
break;
}
}
/**
* Changes the order status, based on the authorization.
*
* @param Authorization $authorization The authorization.
* @param WC_Order $wc_order The WC order.
*
* @throws RuntimeException If payment denied.
*/
protected function handle_authorization_status(
Authorization $authorization,
WC_Order $wc_order
): void {
$status = $authorization->status();
if ( $status->details() ) {
$this->add_status_details_note( $wc_order, $status->name(), $status->details()->reason() );
}
switch ( $status->name() ) {
case AuthorizationStatus::CREATED:
case AuthorizationStatus::PENDING:
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
);
break;
case AuthorizationStatus::DENIED:
$wc_order->update_status(
'failed',
__( 'Could not get the payment authorization.', 'woocommerce-paypal-payments' )
);
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
}
}
/**
* Adds the order note with status details.
*
* @param WC_Order $wc_order The WC order to which the note will be added.
* @param string $status The status name.
* @param string $reason The status reason.
*/
protected function add_status_details_note(
WC_Order $wc_order,
string $status,
string $reason
): void {
$wc_order->add_order_note(
sprintf(
/* translators: %1$s - PENDING, DENIED, ... %2$s - PENDING_REVIEW, ... */
__( 'PayPal order payment is set to %1$s status, details: %2$s.', 'woocommerce-paypal-payments' ),
$status,
$reason
)
);
$wc_order->save();
}
}

View file

@ -183,6 +183,7 @@ class RefundProcessor {
* @return bool
*/
private function is_voidable_authorization( Authorization $authorization ): bool {
return $authorization->status()->is( AuthorizationStatus::CREATED );
return $authorization->status()->is( AuthorizationStatus::CREATED ) ||
$authorization->status()->is( AuthorizationStatus::PENDING );
}
}

View file

@ -8,6 +8,7 @@ use Requests_Utility_CaseInsensitiveDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
@ -288,7 +289,7 @@ class OrderEndpointTest extends TestCase
$expectedOrder->shouldReceive('purchase_units')->once()->andReturn(['0'=>$purchaseUnit]);
$purchaseUnit->shouldReceive('payments')->once()->andReturn($payment);
$payment->shouldReceive('captures')->once()->andReturn(['0'=>$capture]);
$capture->shouldReceive('status')->once()->andReturn('');
$capture->shouldReceive('status')->once()->andReturn(new CaptureStatus(CaptureStatus::COMPLETED));
$result = $testee->capture($orderToCapture);
$this->assertEquals($expectedOrder, $result);

View file

@ -8,10 +8,12 @@ use Psr\Log\NullLogger;
use Requests_Utility_CaseInsensitiveDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ErrorResponseCollection;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use WooCommerce\PayPalCommerce\ApiClient\TestCase;
use Mockery;
@ -21,6 +23,21 @@ use function Brain\Monkey\Functions\expect;
class PaymentsEndpointTest extends TestCase
{
private $authorizationFactory;
private $captureFactory;
private $logger;
public function setUp(): void
{
parent::setUp();
$this->authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$this->captureFactory = Mockery::mock(CaptureFactory::class);
$this->logger = new NullLogger();
}
public function testAuthorizationDefault()
{
expect('wp_json_encode')->andReturnUsing('json_encode');
@ -35,15 +52,10 @@ class PaymentsEndpointTest extends TestCase
->expects('bearer')->andReturn($token);
$authorization = Mockery::mock(Authorization::class);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationFactory
$this->authorizationFactory
->expects('from_paypal_response')
->andReturn($authorization);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$rawResponse = [
@ -54,8 +66,9 @@ class PaymentsEndpointTest extends TestCase
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturnUsing(
@ -92,12 +105,6 @@ class PaymentsEndpointTest extends TestCase
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$logger->shouldReceive('debug');
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$rawResponse = [
@ -108,8 +115,9 @@ class PaymentsEndpointTest extends TestCase
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturn($rawResponse);
@ -131,8 +139,6 @@ class PaymentsEndpointTest extends TestCase
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$rawResponse = [
@ -140,15 +146,12 @@ class PaymentsEndpointTest extends TestCase
'headers' => $headers,
];
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$logger->shouldReceive('debug');
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturn($rawResponse);
@ -172,16 +175,10 @@ class PaymentsEndpointTest extends TestCase
$bearer
->expects('bearer')->andReturn($token);
$authorization = Mockery::mock(Authorization::class);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationFactory
$capture = Mockery::mock(Capture::class);
$this->captureFactory
->expects('from_paypal_response')
->andReturn($authorization);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
->andReturn($capture);
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
@ -193,8 +190,9 @@ class PaymentsEndpointTest extends TestCase
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturnUsing(
@ -219,7 +217,7 @@ class PaymentsEndpointTest extends TestCase
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(201);
$result = $testee->capture($authorizationId);
$this->assertEquals($authorization, $result);
$this->assertEquals($capture, $result);
}
public function testCaptureIsWpError()
@ -234,8 +232,6 @@ class PaymentsEndpointTest extends TestCase
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$rawResponse = [
@ -246,8 +242,9 @@ class PaymentsEndpointTest extends TestCase
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
new NullLogger()
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturn($rawResponse);
@ -269,8 +266,6 @@ class PaymentsEndpointTest extends TestCase
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$rawResponse = [
@ -281,8 +276,9 @@ class PaymentsEndpointTest extends TestCase
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
new NullLogger()
$this->authorizationFactory,
$this->captureFactory,
$this->logger
);
expect('wp_remote_get')->andReturn($rawResponse);

View file

@ -8,6 +8,9 @@ use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
@ -25,6 +28,13 @@ use function Brain\Monkey\Functions\when;
class WcGatewayTest extends TestCase
{
private $environment;
public function setUp(): void {
parent::setUp();
$this->environment = Mockery::mock(Environment::class);
}
public function testProcessPaymentSuccess() {
expect('is_admin')->andReturn(false);
@ -83,6 +93,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
@ -141,6 +152,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
@ -216,10 +228,12 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
$orderEndpoint
$orderEndpoint,
PayPalGateway::ID
);
expect('wc_get_order')
@ -258,11 +272,18 @@ class WcGatewayTest extends TestCase
->expects('save');
$settingsRenderer = Mockery::mock(SettingsRenderer::class);
$orderProcessor = Mockery::mock(OrderProcessor::class);
$capture = Mockery::mock(Capture::class);
$capture
->shouldReceive('status')
->andReturn(new CaptureStatus(CaptureStatus::COMPLETED));
$authorizedPaymentsProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$authorizedPaymentsProcessor
->expects('process')
->with($wcOrder)
->andReturn(AuthorizedPaymentsProcessor::SUCCESSFUL);
$authorizedPaymentsProcessor
->expects('captures')
->andReturn([$capture]);
$authorizedOrderActionNotice = Mockery::mock(AuthorizeOrderActionNotice::class);
$authorizedOrderActionNotice
->expects('display_message')
@ -296,6 +317,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
@ -360,6 +382,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
@ -386,6 +409,9 @@ class WcGatewayTest extends TestCase
->expects('process')
->with($wcOrder)
->andReturn($lastStatus);
$authorizedPaymentsProcessor
->expects('captures')
->andReturn([]);
$authorizedOrderActionNotice = Mockery::mock(AuthorizeOrderActionNotice::class);
$authorizedOrderActionNotice
->expects('display_message')
@ -418,6 +444,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,
@ -467,6 +494,7 @@ class WcGatewayTest extends TestCase
$transactionUrlProvider,
$subscriptionHelper,
PayPalGateway::ID,
$this->environment,
$paymentTokenRepository,
$logger,
$paymentsEndpoint,

View file

@ -10,6 +10,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
@ -57,7 +59,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
$this->paymentsEndpoint
->expects('capture')
->with($this->authorizationId)
->andReturn($this->createAuthorization($this->authorizationId, AuthorizationStatus::CAPTURED));
->andReturn($this->createCapture(CaptureStatus::COMPLETED));
$this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder));
}
@ -78,7 +80,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
$this->paymentsEndpoint
->expects('capture')
->with($authorization->id())
->andReturn($this->createAuthorization($authorization->id(), AuthorizationStatus::CAPTURED));
->andReturn($this->createCapture(CaptureStatus::COMPLETED));
}
$this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $this->testee->process($this->wcOrder));
@ -143,6 +145,14 @@ class AuthorizedPaymentsProcessorTest extends TestCase
return $authorization;
}
private function createCapture(string $status): Capture {
$capture = Mockery::mock(Capture::class);
$capture
->shouldReceive('status')
->andReturn(new CaptureStatus($status));
return $capture;
}
private function createPaypalOrder(array $authorizations): Order {
$payments = Mockery::mock(Payments::class);
$payments

View file

@ -4,15 +4,20 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Dhii\Container\Dictionary;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Woocommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -22,35 +27,44 @@ use function Brain\Monkey\Functions\when;
class OrderProcessorTest extends TestCase
{
private $environment;
public function setUp(): void {
parent::setUp();
$this->environment = new Environment(new Dictionary([]));
}
public function testAuthorize() {
$transactionId = 'ABC123';
$capture = Mockery::mock(Capture::class);
$capture->expects('id')
$authorization = Mockery::mock(Authorization::class);
$authorization->shouldReceive('id')
->andReturn($transactionId);
$authorization->shouldReceive('status')
->andReturn(new AuthorizationStatus(AuthorizationStatus::CREATED));
$payments = Mockery::mock(Payments::class);
$payments->expects('captures')
->andReturn([$capture]);
$payments->shouldReceive('authorizations')
->andReturn([$authorization]);
$payments->shouldReceive('captures')
->andReturn([]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->expects('payments')
$purchaseUnit->shouldReceive('payments')
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->expects('set_transaction_id')
->with($transactionId);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
->expects('is')
->shouldReceive('is')
->with(OrderStatus::APPROVED)
->andReturn(true);
$orderStatus
->expects('is')
->shouldReceive('is')
->with(OrderStatus::COMPLETED)
->andReturn(true);
@ -67,7 +81,7 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('status')
->andReturn($orderStatus);
$currentOrder->expects('purchase_units')
$currentOrder->shouldReceive('purchase_units')
->andReturn([$purchaseUnit]);
$sessionHandler = Mockery::mock(SessionHandler::class);
@ -112,7 +126,7 @@ class OrderProcessorTest extends TestCase
$authorizedPaymentProcessor,
$settings,
$logger,
false
$this->environment
);
$cart = Mockery::mock(\WC_Cart::class);
@ -156,23 +170,25 @@ class OrderProcessorTest extends TestCase
$capture = Mockery::mock(Capture::class);
$capture->expects('id')
->andReturn($transactionId);
$capture->expects('status')
->andReturn(new CaptureStatus(CaptureStatus::COMPLETED));
$payments = Mockery::mock(Payments::class);
$payments->expects('captures')
$payments->shouldReceive('captures')
->andReturn([$capture]);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->expects('payments')
$purchaseUnit->shouldReceive('payments')
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
$orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
->expects('is')
->shouldReceive('is')
->with(OrderStatus::APPROVED)
->andReturn(true);
$orderStatus
->expects('is')
->shouldReceive('is')
->with(OrderStatus::COMPLETED)
->andReturn(true);
$orderId = 'abc';
@ -188,7 +204,7 @@ class OrderProcessorTest extends TestCase
->shouldReceive('status')
->andReturn($orderStatus);
$currentOrder
->expects('purchase_units')
->shouldReceive('purchase_units')
->andReturn([$purchaseUnit]);
$sessionHandler = Mockery::mock(SessionHandler::class);
$sessionHandler
@ -240,7 +256,7 @@ class OrderProcessorTest extends TestCase
$authorizedPaymentProcessor,
$settings,
$logger,
false
$this->environment
);
$cart = Mockery::mock(\WC_Cart::class);
@ -264,9 +280,6 @@ class OrderProcessorTest extends TestCase
PayPalGateway::INTENT_META_KEY,
$orderIntent
);
$wcOrder
->expects('update_status')
->with('on-hold', 'Awaiting payment.');
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->expects('set_transaction_id')
@ -340,7 +353,7 @@ class OrderProcessorTest extends TestCase
$authorizedPaymentProcessor,
$settings,
$logger,
false
$this->environment
);
$wcOrder