Merge branch 'master' into feature/create-order-intent-authorize

# Conflicts:
#	modules.local/ppcp-api-client/services.php
#	modules.local/ppcp-api-client/src/Endpoint/OrderEndpoint.php
#	modules.local/ppcp-wc-gateway/src/WcGatewayModule.php
This commit is contained in:
Mészáros Róbert 2020-04-13 15:22:36 +03:00
commit 8627fddede
38 changed files with 1202 additions and 96 deletions

View file

@ -3,14 +3,14 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
class CacheClearTest extends TestCase
{
public function testClear() {
public function testClear()
{
$testee = new Cache('group');
expect('wp_cache_flush')
->once()
@ -18,11 +18,12 @@ class CacheClearTest extends TestCase
$this->assertTrue($testee->clear());
}
public function testClearReturnsFalseWhenCacheWasNotCleared() {
public function testClearReturnsFalseWhenCacheWasNotCleared()
{
$testee = new Cache('group');
expect('wp_cache_flush')
->once()
->andReturn(false);
$this->assertFalse($testee->clear());
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class CacheDeleteTest extends TestCase
{
public function testDelete() {
public function testDelete()
{
$testee = new Cache('group');
expect('wp_cache_delete')
->once()
@ -20,7 +20,8 @@ class CacheDeleteTest extends TestCase
$this->assertTrue($testee->delete('key'));
}
public function testDeleteFails() {
public function testDeleteFails()
{
$testee = new Cache('group');
expect('wp_cache_delete')
->once()
@ -29,9 +30,10 @@ class CacheDeleteTest extends TestCase
$this->assertFalse($testee->delete('key'));
}
public function testDeleteThrowsErrorIfKeyIsNotAString() {
public function testDeleteThrowsErrorIfKeyIsNotAString()
{
$testee = new Cache('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->delete(123);
}
}
}

View file

@ -10,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class CacheGetTest extends TestCase
{
public function testGetHasValueInCache() {
public function testGetHasValueInCache()
{
$this->markTestIncomplete(
'This test has not been implemented yet. Problem is the $found reference'
);
@ -18,7 +19,7 @@ class CacheGetTest extends TestCase
$expected = 'value';
expect('wp_cache_get')
->once()
->andReturnUsing(function($key, $group, $force, &$lastFound) use ($expected) {
->andReturnUsing(function ($key, $group, $force, &$lastFound) use ($expected) {
$lastFound = true;
return $expected;
});
@ -26,7 +27,8 @@ class CacheGetTest extends TestCase
$this->assertEquals($expected, $result);
}
public function testGetHasValueNotInCache() {
public function testGetHasValueNotInCache()
{
$this->markTestIncomplete(
'This test has not been implemented yet. Problem is the $found reference'
);
@ -40,10 +42,10 @@ class CacheGetTest extends TestCase
$this->assertEquals($expected, $result);
}
public function testGetThrowsExceptionWhenKeyIsNotAString() {
public function testGetThrowsExceptionWhenKeyIsNotAString()
{
$testee = new Cache('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->get(123);
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class CacheSetTest extends TestCase
{
public function testSet() {
public function testSet()
{
$testee = new Cache('group');
expect('wp_cache_set')
->once()
@ -21,7 +21,8 @@ class CacheSetTest extends TestCase
$this->assertTrue($setValue);
}
public function testSetReturnsFalseWhenCacheNotSet() {
public function testSetReturnsFalseWhenCacheNotSet()
{
$testee = new Cache('group');
expect('wp_cache_set')
->once()
@ -31,9 +32,10 @@ class CacheSetTest extends TestCase
$this->assertFalse($setValue);
}
public function testSetThrowsErrorWhenKeyIsNotAString() {
public function testSetThrowsErrorWhenKeyIsNotAString()
{
$testee = new Cache('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->set(123, 'value');
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,8 +10,8 @@ use function Brain\Monkey\Functions\expect;
class DeleteMultipleTest extends TestCase
{
public function testDeleteMultiple() {
public function testDeleteMultiple()
{
$testee = new Transient('group');
$keys = ['key1', 'key2'];
expect('delete_transient')->once()->with('groupkey1')->andReturn(true);
@ -20,18 +19,20 @@ class DeleteMultipleTest extends TestCase
$this->assertTrue($testee->deleteMultiple($keys));
}
public function testDeleteMultipleThrowsErrorIfKeysAreNotIterateable() {
public function testDeleteMultipleThrowsErrorIfKeysAreNotIterateable()
{
$testee = new Transient('group');
$keys = new \stdClass();
$this->expectException(InvalidCacheArgumentException::class);
$testee->deleteMultiple($keys);
}
public function testDeleteMultipleThrowsErrorIfKeysAreNotStrings() {
public function testDeleteMultipleThrowsErrorIfKeysAreNotStrings()
{
$testee = new Transient('group');
$keys = [1];
$this->expectException(InvalidCacheArgumentException::class);
$testee->deleteMultiple($keys);
}
}
}

View file

@ -10,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class GetMultipleTest extends TestCase
{
public function testGetMultiple() {
public function testGetMultiple()
{
$testee = new Transient('group');
expect('get_transient')
->times(3)
@ -27,17 +28,20 @@ class GetMultipleTest extends TestCase
$this->assertTrue($value === 1);
}
}
public function testGetMultipleThrowsErrorIfParamIsNotIterateable() {
public function testGetMultipleThrowsErrorIfParamIsNotIterateable()
{
$testee = new Transient('group');
$this->expectException(InvalidCacheArgumentException::class);
$keys = new \stdClass();
$testee->getMultiple($keys);
}
public function testGetMultipleThrowsErrorIfOneKeyIsNotAString() {
public function testGetMultipleThrowsErrorIfOneKeyIsNotAString()
{
$testee = new Transient('group');
$this->expectException(InvalidCacheArgumentException::class);
$keys = [1];
$testee->getMultiple($keys);
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class SetMultipleTest extends TestCase
{
public function testSetMultiple() {
public function testSetMultiple()
{
$testee = new Transient('group');
$values = [
'key1' => 'value1',
@ -21,7 +21,7 @@ class SetMultipleTest extends TestCase
expect('set_transient')
->times(3)
->andReturnUsing(
function($key, $value) use ($values) {
function ($key, $value) use ($values) {
$key = str_replace('group', '', $key);
return isset($values[$key]) && $values[$key] === $value;
}
@ -30,17 +30,19 @@ class SetMultipleTest extends TestCase
$this->assertTrue($testee->setMultiple($values));
}
public function testSetMultipleThrowsErrorIfNotIterateable() {
public function testSetMultipleThrowsErrorIfNotIterateable()
{
$testee = new Transient('group');
$values = new \stdClass();
$this->expectException(InvalidCacheArgumentException::class);
$testee->setMultiple($values);
}
public function testSetMultipleThrowsErrorIfKeyIsNotString() {
public function testSetMultipleThrowsErrorIfKeyIsNotString()
{
$testee = new Transient('group');
$values = [1];
$this->expectException(InvalidCacheArgumentException::class);
$testee->setMultiple($values);
}
}
}

View file

@ -3,14 +3,14 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
class TransientClearTest extends TestCase
{
public function testClearWithObjectCache() {
public function testClearWithObjectCache()
{
$testee = new Transient('group');
expect('wp_using_ext_object_cache')
->once()
@ -20,7 +20,9 @@ class TransientClearTest extends TestCase
->andReturn(true);
$this->assertTrue($testee->clear());
}
public function testClearWithObjectCacheFails() {
public function testClearWithObjectCacheFails()
{
$testee = new Transient('group');
expect('wp_using_ext_object_cache')
->once()
@ -31,7 +33,8 @@ class TransientClearTest extends TestCase
$this->assertFalse($testee->clear());
}
public function testClearReturnsFalseWhenObjectCacheIsNotUsed() {
public function testClearReturnsFalseWhenObjectCacheIsNotUsed()
{
$testee = new Transient('group');
expect('wp_using_ext_object_cache')
->once()
@ -41,4 +44,4 @@ class TransientClearTest extends TestCase
->andReturn(true);
$this->assertFalse($testee->clear());
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class TransientDeleteTest extends TestCase
{
public function testDelete() {
public function testDelete()
{
$testee = new Transient('group');
expect('delete_transient')
->once()
@ -20,7 +20,8 @@ class TransientDeleteTest extends TestCase
$this->assertTrue($testee->delete('key'));
}
public function testDeleteFails() {
public function testDeleteFails()
{
$testee = new Transient('group');
expect('delete_transient')
->once()
@ -29,10 +30,10 @@ class TransientDeleteTest extends TestCase
$this->assertFalse($testee->delete('key'));
}
public function testDeleteThrowsErrorIfKeyIsNotAString() {
public function testDeleteThrowsErrorIfKeyIsNotAString()
{
$testee = new Transient('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->delete(123);
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class TransientGetTest extends TestCase
{
public function testGetHasValueInCache() {
public function testGetHasValueInCache()
{
$testee = new Transient('group');
$expected = 'value';
expect('get_transient')
@ -22,7 +22,8 @@ class TransientGetTest extends TestCase
$this->assertEquals($expected, $result);
}
public function testGetHasValueNotInCache() {
public function testGetHasValueNotInCache()
{
$testee = new Transient('group');
$expected = 'value';
expect('get_transient')
@ -33,10 +34,10 @@ class TransientGetTest extends TestCase
$this->assertEquals($expected, $result);
}
public function testGetThrowsExceptionWhenKeyIsNotAString() {
public function testGetThrowsExceptionWhenKeyIsNotAString()
{
$testee = new Transient('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->get(123);
}
}
}

View file

@ -3,16 +3,16 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
class TransientHasTest extends TestCase
{
public function testHas() {
public function testHas()
{
$testee = new Transient('group');
expect('get_transient')->with('groupkey')->andReturn(1);
$this->assertTrue($testee->has('key'));
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Cache;
use Inpsyde\CacheModule\Exception\InvalidCacheArgumentException;
use PHPUnit\Framework\TestCase;
use function Brain\Monkey\Functions\expect;
@ -11,7 +10,8 @@ use function Brain\Monkey\Functions\expect;
class TransientSetTest extends TestCase
{
public function testSet() {
public function testSet()
{
$testee = new Transient('group');
expect('set_transient')
->once()
@ -21,7 +21,8 @@ class TransientSetTest extends TestCase
$this->assertTrue($setValue);
}
public function testSetReturnsFalseWhenCacheNotSet() {
public function testSetReturnsFalseWhenCacheNotSet()
{
$testee = new Transient('group');
expect('set_transient')
->once()
@ -31,9 +32,10 @@ class TransientSetTest extends TestCase
$this->assertFalse($setValue);
}
public function testSetThrowsErrorWhenKeyIsNotAString() {
public function testSetThrowsErrorWhenKeyIsNotAString()
{
$testee = new Transient('group');
$this->expectException(InvalidCacheArgumentException::class);
$testee->set(123, 'value');
}
}
}

View file

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Inpsyde\CacheModule\Provider;
use Inpsyde\CacheModule\Cache\Cache;
use Inpsyde\CacheModule\Cache\Transient;
use PHPUnit\Framework\TestCase;
@ -12,7 +11,8 @@ use function Brain\Monkey\Functions\expect;
class CacheProviderTest extends TestCase
{
public function test_transientForKey() {
public function test_transientForKey()
{
$testee = new CacheProvider();
$result = $testee->transientForKey('group');
@ -23,7 +23,9 @@ class CacheProviderTest extends TestCase
$this->assertTrue($result->set('key', 'value'), 'Group has not been set correctly.');
$this->assertTrue(is_a($result, Transient::class));
}
public function test_cacheOrTransientForKeyReturnsCache() {
public function test_cacheOrTransientForKeyReturnsCache()
{
$testee = new CacheProvider();
expect('wp_using_ext_object_cache')
->once()
@ -32,7 +34,9 @@ class CacheProviderTest extends TestCase
$this->assertInstanceOf(Cache::class, $result);
}
public function test_cacheOrTransientForKeyReturnsTransient() {
public function test_cacheOrTransientForKeyReturnsTransient()
{
$testee = new CacheProvider();
expect('wp_using_ext_object_cache')
->once()
@ -41,7 +45,8 @@ class CacheProviderTest extends TestCase
$this->assertInstanceOf(Transient::class, $result);
}
public function test_cacheForKey() {
public function test_cacheForKey()
{
$testee = new CacheProvider();
$result = $testee->cacheForKey('group');
expect('wp_cache_set')
@ -51,4 +56,4 @@ class CacheProviderTest extends TestCase
$this->assertTrue($result->set('key', 'value'), 'Group has not been set correctly.');
$this->assertTrue(is_a($result, Cache::class));
}
}
}

View file

@ -7,6 +7,12 @@
"inpsyde/ppcp-session": "dev-master",
"inpsyde/cache-module": "dev-master"
},
"require-dev": {
"inpsyde/php-coding-standards": "@stable",
"phpunit/phpunit": "^9.1",
"brain/monkey": "^2.4"
},
"autoload": {
"psr-4": {
"Inpsyde\\PayPalCommerce\\ApiClient\\": "src/"

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="vendor/autoload.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="unit">
<directory suffix="Test.php">./tests/PHPUnit</directory>
</testsuite>
</testsuites>
</phpunit>

View file

@ -9,6 +9,7 @@ use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AddressFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AmountFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ItemFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\LineItemFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
@ -46,6 +47,7 @@ return [
'api.endpoint.order' => function (ContainerInterface $container) : OrderEndpoint {
$orderFactory = $container->get('api.factory.order');
$patchCollectionFactory = $container->get('api.factory.patch-collection-factory');
$errorResponseFactory = $container->get('api.factory.response-error');
// TODO: get the settings using the class
// Using it now throws a maximum nested error because they share the same dependency
@ -56,7 +58,8 @@ return [
$container->get('api.bearer'),
$orderFactory,
$patchCollectionFactory,
$intent
$intent,
$errorResponseFactory
);
},
'api.cart-repository' => function (ContainerInterface $container) : CartRepository {
@ -113,6 +116,9 @@ return [
'api.factory.address' => function (ContainerInterface $container) : AddressFactory {
return new AddressFactory();
},
'api.factory.response-error' => function (ContainerInterface $container) : ErrorResponseCollectionFactory {
return new ErrorResponseCollectionFactory();
},
'api.factory.order' => function (ContainerInterface $container) : OrderFactory {
$purchaseUnitFactory = $container->get('api.factory.purchase-unit');
$payerFactory = $container->get('api.factory.payer');

View file

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ErrorResponse;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
@ -19,13 +21,15 @@ class OrderEndpoint
private $orderFactory;
private $patchCollectionFactory;
private $intent;
private $errorResponseFactory;
public function __construct(
string $host,
Bearer $bearer,
OrderFactory $orderFactory,
PatchCollectionFactory $patchCollectionFactory,
string $intent
string $intent,
ErrorResponseCollectionFactory $errorResponseFactory
) {
$this->host = $host;
@ -33,6 +37,7 @@ class OrderEndpoint
$this->orderFactory = $orderFactory;
$this->patchCollectionFactory = $patchCollectionFactory;
$this->intent = $intent;
$this->errorResponseFactory = $errorResponseFactory;
}
public function createForPurchaseUnits(PurchaseUnit ...$items) : Order
@ -57,10 +62,21 @@ class OrderEndpoint
'body' => json_encode($data),
];
$response = wp_remote_post($url, $args);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 201) {
if (is_wp_error($response)) {
$this->handleResponseWpError($url, $args);
throw new RuntimeException(__('Could not create order.', 'woocommerce-paypal-commerce-gateway'));
}
$json = json_decode($response['body']);
if (wp_remote_retrieve_response_code($response) !== 201) {
$errors = $this->errorResponseFactory->fromPayPalResponse(
$json,
(int)wp_remote_retrieve_response_code($response),
$url,
$args
);
add_action('woocommerce-paypal-commerce-gateway.error', $errors);
throw new RuntimeException(__('Could not create order.', 'woocommerce-paypal-commerce-gateway'));
}
$order = $this->orderFactory->fromPayPalResponse($json);
return $order;
}
@ -82,22 +98,24 @@ class OrderEndpoint
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
$this->handleResponseWpError($url, $args);
throw new RuntimeException(__('Could not capture order.', 'woocommerce-paypal-commerce-gateway'));
}
if (wp_remote_retrieve_response_code($response) !== 422) {
$json = json_decode($response['body']);
if (is_array($json->details) && count(array_filter(
$json->details,
function ($detail) : bool {
return $detail->issue === 'ORDER_ALREADY_CAPTURED';
}
))
) {
$json = json_decode($response['body']);
if (wp_remote_retrieve_response_code($response) !== 201) {
$errors = $this->errorResponseFactory->fromPayPalResponse(
$json,
(int)wp_remote_retrieve_response_code($response),
$url,
$args
);
// If the order has already been captured, we return the updated order.
if ($errors->hasErrorCode(ErrorResponse::ORDER_ALREADY_CAPTURED)) {
return $this->order($order->id());
}
}
if (wp_remote_retrieve_response_code($response) !== 201) {
add_action('woocommerce-paypal-commerce-gateway.error', $errors);
throw new RuntimeException(__('Could not capture order.', 'woocommerce-paypal-commerce-gateway'));
}
$json = json_decode($response['body']);
@ -137,10 +155,21 @@ class OrderEndpoint
],
];
$response = wp_remote_get($url, $args);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
if (is_wp_error($response)) {
$this->handleResponseWpError($url, $args);
throw new RuntimeException(__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway'));
}
$json = json_decode($response['body']);
if (wp_remote_retrieve_response_code($response) !== 200) {
$errors = $this->errorResponseFactory->fromPayPalResponse(
$json,
(int) wp_remote_retrieve_response_code($response),
$url,
$args
);
add_action('woocommerce-paypal-commerce-gateway.error', $errors);
throw new RuntimeException(__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway'));
}
return $this->orderFactory->fromPayPalResponse($json);
}
@ -163,11 +192,33 @@ class OrderEndpoint
'body' => json_encode($patches->toArray()),
];
$response = wp_remote_post($url, $args);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 204) {
if (is_wp_error($response)) {
$this->handleResponseWpError($url, $args);
throw new RuntimeException(__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway'));
}
if (wp_remote_retrieve_response_code($response) !== 204) {
$json = json_decode($response['body']);
$errors = $this->errorResponseFactory->fromPayPalResponse(
$json,
(int)wp_remote_retrieve_response_code($response),
$url,
$args
);
add_action('woocommerce-paypal-commerce-gateway.error', $errors);
throw new RuntimeException(__('Could not patch order.', 'woocommerce-paypal-commerce-gateway'));
}
$newOrder = $this->order($orderToUpdate->id());
return $newOrder;
}
private function handleResponseWpError(string $url, array $args)
{
$errors = $this->errorResponseFactory->unknownError(
$url,
$args
);
add_action('woocommerce-paypal-commerce-gateway.error', $errors);
}
}

View file

@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class ErrorResponse
{
const UNKNOWN = 'UNKNOWN';
/* Order error codes */
const ACTION_DOES_NOT_MATCH_INTENT='ACTION_DOES_NOT_MATCH_INTENT';
const AGREEMENT_ALREADY_CANCELLED='AGREEMENT_ALREADY_CANCELLED';
const AMOUNT_CANNOT_BE_SPECIFIED='AMOUNT_CANNOT_BE_SPECIFIED';
const AMOUNT_MISMATCH='AMOUNT_MISMATCH';
const AMOUNT_NOT_PATCHABLE='AMOUNT_NOT_PATCHABLE';
const AUTH_CAPTURE_NOT_ENABLED='AUTH_CAPTURE_NOT_ENABLED';
const AUTHENTICATION_FAILURE='AUTHENTICATION_FAILURE';
const AUTHORIZATION_AMOUNT_EXCEEDED='AUTHORIZATION_AMOUNT_EXCEEDED';
const AUTHORIZATION_CURRENCY_MISMATCH='AUTHORIZATION_CURRENCY_MISMATCH';
const BILLING_AGREEMENT_NOT_FOUND='BILLING_AGREEMENT_NOT_FOUND';
const CANNOT_BE_NEGATIVE='CANNOT_BE_NEGATIVE';
const CANNOT_BE_ZERO_OR_NEGATIVE='CANNOT_BE_ZERO_OR_NEGATIVE';
const CARD_TYPE_NOT_SUPPORTED='CARD_TYPE_NOT_SUPPORTED';
const INVALID_SECURITY_CODE_LENGTH='INVALID_SECURITY_CODE_LENGTH';
const CITY_REQUIRED='CITY_REQUIRED';
const COMPLIANCE_VIOLATION='COMPLIANCE_VIOLATION';
const CONSENT_NEEDED='CONSENT_NEEDED';
const CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE='CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE';
const CURRENCY_NOT_SUPPORTED_FOR_COUNTRY='CURRENCY_NOT_SUPPORTED_FOR_COUNTRY';
const DECIMAL_PRECISION='DECIMAL_PRECISION';
const DOMESTIC_TRANSACTION_REQUIRED='DOMESTIC_TRANSACTION_REQUIRED';
const DUPLICATE_INVOICE_ID='DUPLICATE_INVOICE_ID';
const DUPLICATE_REQUEST_ID='DUPLICATE_REQUEST_ID';
const FIELD_NOT_PATCHABLE='FIELD_NOT_PATCHABLE';
const INSTRUMENT_DECLINED='INSTRUMENT_DECLINED';
const INTERNAL_SERVER_ERROR='INTERNAL_SERVER_ERROR';
const INTERNAL_SERVICE_ERROR='INTERNAL_SERVICE_ERROR';
const INVALID_ACCOUNT_STATUS='INVALID_ACCOUNT_STATUS';
const INVALID_ARRAY_MAX_ITEMS='INVALID_ARRAY_MAX_ITEMS';
const INVALID_ARRAY_MIN_ITEMS='INVALID_ARRAY_MIN_ITEMS';
const INVALID_COUNTRY_CODE='INVALID_COUNTRY_CODE';
const INVALID_CURRENCY_CODE='INVALID_CURRENCY_CODE';
const INVALID_JSON_POINTER_FORMAT='INVALID_JSON_POINTER_FORMAT';
const INVALID_PARAMETER_SYNTAX='INVALID_PARAMETER_SYNTAX';
const INVALID_PARAMETER_VALUE='INVALID_PARAMETER_VALUE';
const INVALID_PARAMETER='INVALID_PARAMETER';
const INVALID_PATCH_OPERATION='INVALID_PATCH_OPERATION';
const INVALID_PAYER_ID='INVALID_PAYER_ID';
const INVALID_RESOURCE_ID='INVALID_RESOURCE_ID';
const INVALID_STRING_LENGTH='INVALID_STRING_LENGTH';
const ITEM_TOTAL_MISMATCH='ITEM_TOTAL_MISMATCH';
const ITEM_TOTAL_REQUIRED='ITEM_TOTAL_REQUIRED';
const MAX_AUTHORIZATION_COUNT_EXCEEDED='MAX_AUTHORIZATION_COUNT_EXCEEDED';
const MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED='MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED';
const MAX_VALUE_EXCEEDED='MAX_VALUE_EXCEEDED';
const MISSING_REQUIRED_PARAMETER='MISSING_REQUIRED_PARAMETER';
const MISSING_SHIPPING_ADDRESS='MISSING_SHIPPING_ADDRESS';
const MULTI_CURRENCY_ORDER='MULTI_CURRENCY_ORDER';
const MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED='MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED';
const MULTIPLE_SHIPPING_OPTION_SELECTED='MULTIPLE_SHIPPING_OPTION_SELECTED';
const INVALID_PICKUP_ADDRESS='INVALID_PICKUP_ADDRESS';
const NOT_AUTHORIZED='NOT_AUTHORIZED';
const NOT_ENABLED_FOR_CARD_PROCESSING='NOT_ENABLED_FOR_CARD_PROCESSING';
const NOT_PATCHABLE='NOT_PATCHABLE';
const NOT_SUPPORTED='NOT_SUPPORTED';
const ORDER_ALREADY_AUTHORIZED='ORDER_ALREADY_AUTHORIZED';
const ORDER_ALREADY_CAPTURED='ORDER_ALREADY_CAPTURED';
const ORDER_ALREADY_COMPLETED='ORDER_ALREADY_COMPLETED';
const ORDER_CANNOT_BE_SAVED='ORDER_CANNOT_BE_SAVED';
const ORDER_COMPLETED_OR_VOIDED='ORDER_COMPLETED_OR_VOIDED';
const ORDER_EXPIRED='ORDER_EXPIRED';
const ORDER_NOT_APPROVED='ORDER_NOT_APPROVED';
const ORDER_NOT_SAVED='ORDER_NOT_SAVED';
const ORDER_PREVIOUSLY_VOIDED='ORDER_PREVIOUSLY_VOIDED';
const PARAMETER_VALUE_NOT_SUPPORTED='PARAMETER_VALUE_NOT_SUPPORTED';
const PATCH_PATH_REQUIRED='PATCH_PATH_REQUIRED';
const PATCH_VALUE_REQUIRED='PATCH_VALUE_REQUIRED';
const PAYEE_ACCOUNT_INVALID='PAYEE_ACCOUNT_INVALID';
const PAYEE_ACCOUNT_LOCKED_OR_CLOSED='PAYEE_ACCOUNT_LOCKED_OR_CLOSED';
const PAYEE_ACCOUNT_RESTRICTED='PAYEE_ACCOUNT_RESTRICTED';
const PAYEE_BLOCKED_TRANSACTION='PAYEE_BLOCKED_TRANSACTION';
const PAYER_ACCOUNT_LOCKED_OR_CLOSED='PAYER_ACCOUNT_LOCKED_OR_CLOSED';
const PAYER_ACCOUNT_RESTRICTED='PAYER_ACCOUNT_RESTRICTED';
const PAYER_CANNOT_PAY='PAYER_CANNOT_PAY';
const PAYER_CONSENT_REQUIRED='PAYER_CONSENT_REQUIRED';
const PAYER_COUNTRY_NOT_SUPPORTED='PAYER_COUNTRY_NOT_SUPPORTED';
const PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING='PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING';
const PAYMENT_INSTRUCTION_REQUIRED='PAYMENT_INSTRUCTION_REQUIRED';
const PERMISSION_DENIED='PERMISSION_DENIED';
const POSTAL_CODE_REQUIRED='POSTAL_CODE_REQUIRED';
const PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH='PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH';
const REDIRECT_PAYER_FOR_ALTERNATE_FUNDING='REDIRECT_PAYER_FOR_ALTERNATE_FUNDING';
const REFERENCE_ID_NOT_FOUND='REFERENCE_ID_NOT_FOUND';
const REFERENCE_ID_REQUIRED='REFERENCE_ID_REQUIRED';
const DUPLICATE_REFERENCE_ID='DUPLICATE_REFERENCE_ID';
const SHIPPING_ADDRESS_INVALID='SHIPPING_ADDRESS_INVALID';
const SHIPPING_OPTION_NOT_SELECTED='SHIPPING_OPTION_NOT_SELECTED';
const SHIPPING_OPTIONS_NOT_SUPPORTED='SHIPPING_OPTIONS_NOT_SUPPORTED';
const TAX_TOTAL_MISMATCH='TAX_TOTAL_MISMATCH';
const TAX_TOTAL_REQUIRED='TAX_TOTAL_REQUIRED';
const TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT='TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT';
const TRANSACTION_BLOCKED_BY_PAYEE='TRANSACTION_BLOCKED_BY_PAYEE';
const TRANSACTION_LIMIT_EXCEEDED='TRANSACTION_LIMIT_EXCEEDED';
const TRANSACTION_RECEIVING_LIMIT_EXCEEDED='TRANSACTION_RECEIVING_LIMIT_EXCEEDED';
const TRANSACTION_REFUSED='TRANSACTION_REFUSED';
const UNSUPPORTED_INTENT='UNSUPPORTED_INTENT';
const UNSUPPORTED_PATCH_PARAMETER_VALUE='UNSUPPORTED_PATCH_PARAMETER_VALUE';
const UNSUPPORTED_PAYMENT_INSTRUCTION='UNSUPPORTED_PAYMENT_INSTRUCTION';
const PAYEE_ACCOUNT_NOT_SUPPORTED='PAYEE_ACCOUNT_NOT_SUPPORTED';
const PAYEE_ACCOUNT_NOT_VERIFIED='PAYEE_ACCOUNT_NOT_VERIFIED';
const PAYEE_NOT_CONSENTED='PAYEE_NOT_CONSENTED';
private $code;
private $message;
private $httpCode;
private $url;
private $args;
public function __construct(string $code, string $message, int $httpCode, string $url, array $args)
{
$this->code = $code;
$this->message = $message;
$this->httpCode = $httpCode;
$this->url = $url;
$this->args = $args;
}
public function code() : string
{
return $this->code;
}
public function is(string $compare) : bool
{
return $this->code() === $compare;
}
public function message() : string
{
return $this->message;
}
public function httpCode() : int
{
return $this->httpCode;
}
public function url() : string
{
return $this->url;
}
public function args() : array
{
return $this->args;
}
}

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class ErrorResponseCollection
{
private $errors;
public function __construct(ErrorResponse ...$errors)
{
$this->errors = $errors;
}
/**
* @return ErrorResponse[]
*/
public function errors() : array
{
return $this->errors;
}
public function codes() : array
{
return array_values(array_map(
function(ErrorResponse $error) : string {
return $error->code();
},
$this->errors()
));
}
public function hasErrorCode(string $code) : bool
{
return in_array($code, $this->codes(), true);
}
}

View file

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ErrorResponse;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ErrorResponseCollection;
class ErrorResponseCollectionFactory
{
public function fromPayPalResponse(
\stdClass $response,
int $statusCode,
string $url,
array $args
) : ErrorResponseCollection {
if (isset($response->error)) {
return new ErrorResponseCollection(
new ErrorResponse(
(string) $response->error,
(isset($response->error->description)) ? (string) $response->error->description : '',
$statusCode,
$url,
$args
)
);
}
if (! isset($response->details) || ! is_array($response->details)) {
return new ErrorResponseCollection();
}
$errors = [];
foreach ($response->details as $detail) {
$errors[] = new ErrorResponse(
(string) $detail->issue,
(string) $detail->description,
$statusCode,
$url,
$args
);
}
return new ErrorResponseCollection(... $errors);
}
public function unknownError(string $url, array $args) : ErrorResponseCollection
{
return new ErrorResponseCollection(
new ErrorResponse(
ErrorResponse::UNKNOWN,
'unknown',
0,
$url,
$args
)
);
}
}

View file

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class AddressTest extends TestCase
{
public function test() {
$testee = new Address(
'countryCode',
'addressLine1',
'addressLine2',
'adminArea1',
'adminArea2',
'postalCode'
);
$this->assertEquals('countryCode', $testee->countryCode());
$this->assertEquals('addressLine1', $testee->addressLine1());
$this->assertEquals('addressLine2', $testee->addressLine2());
$this->assertEquals('adminArea1', $testee->adminArea1());
$this->assertEquals('adminArea2', $testee->adminArea2());
$this->assertEquals('postalCode', $testee->postalCode());
}
public function testToArray()
{
$testee = new Address(
'countryCode',
'addressLine1',
'addressLine2',
'adminArea1',
'adminArea2',
'postalCode'
);
$expected = [
'country_code' => 'countryCode',
'address_line_1' => 'addressLine1',
'address_line_2' => 'addressLine2',
'admin_area_1' => 'adminArea1',
'admin_area_2' => 'adminArea2',
'postal_code' => 'postalCode',
];
$actual = $testee->toArray();
$this->assertEquals($expected, $actual);
}
}

View file

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class AmountBreakdownTest extends TestCase
{
public function test() {
$itemTotal = \Mockery::mock(Money::class);
$itemTotal
->expects('toArray')->andReturn(['itemTotal']);
$shipping = \Mockery::mock(Money::class);
$shipping
->expects('toArray')->andReturn(['shipping']);
$taxTotal = \Mockery::mock(Money::class);
$taxTotal
->expects('toArray')->andReturn(['taxTotal']);
$handling = \Mockery::mock(Money::class);
$handling
->expects('toArray')->andReturn(['handling']);
$insurance = \Mockery::mock(Money::class);
$insurance
->expects('toArray')->andReturn(['insurance']);
$shippingDiscount = \Mockery::mock(Money::class);
$shippingDiscount
->expects('toArray')->andReturn(['shippingDiscount']);
$discount = \Mockery::mock(Money::class);
$discount
->expects('toArray')->andReturn(['discount']);
$testee = new AmountBreakdown(
$itemTotal,
$shipping,
$taxTotal,
$handling,
$insurance,
$shippingDiscount,
$discount
);
$this->assertEquals($itemTotal, $testee->itemTotal());
$this->assertEquals($shipping, $testee->shipping());
$this->assertEquals($taxTotal, $testee->taxTotal());
$this->assertEquals($handling, $testee->handling());
$this->assertEquals($insurance, $testee->insurance());
$this->assertEquals($shippingDiscount, $testee->shippingDiscount());
$this->assertEquals($discount, $testee->discount());
$expected = [
'item_total' => ['itemTotal'],
'shipping' => ['shipping'],
'tax_total' => ['taxTotal'],
'handling' => ['handling'],
'insurance' => ['insurance'],
'shipping_discount' => ['shippingDiscount'],
'discount' => ['discount'],
];
$this->assertEquals($expected, $testee->toArray());
}
/**
* @dataProvider dataDropArrayKeyIfNoValueGiven
*/
public function testDropArrayKeyIfNoValueGiven($keyMissing, $methodName) {
$itemTotal = \Mockery::mock(Money::class);
$itemTotal
->expects('toArray')->andReturn(['itemTotal']);
$shipping = \Mockery::mock(Money::class);
$shipping
->expects('toArray')->andReturn(['shipping']);
$taxTotal = \Mockery::mock(Money::class);
$taxTotal
->expects('toArray')->andReturn(['taxTotal']);
$handling = \Mockery::mock(Money::class);
$handling
->expects('toArray')->andReturn(['handling']);
$insurance = \Mockery::mock(Money::class);
$insurance
->expects('toArray')->andReturn(['insurance']);
$shippingDiscount = \Mockery::mock(Money::class);
$shippingDiscount
->expects('toArray')->andReturn(['shippingDiscount']);
$discount = \Mockery::mock(Money::class);
$discount
->expects('toArray')->andReturn(['discount']);
$items = [
'item_total' => $itemTotal,
'shipping' => $shipping,
'tax_total' => $taxTotal,
'handling' => $handling,
'insurance' => $insurance,
'shipping_discount' => $shippingDiscount,
'discount' => $discount,
];
$items[$keyMissing] = null;
$testee = new AmountBreakdown(...array_values($items));
$array = $testee->toArray();
$result = ! array_key_exists($keyMissing, $array);
$this->assertTrue($result);
$this->assertNull($testee->{$methodName}(), "$methodName should return null");
}
public function dataDropArrayKeyIfNoValueGiven() : array {
return [
['item_total', 'itemTotal'],
['shipping', 'shipping'],
['tax_total', 'taxTotal'],
['handling', 'handling'],
['insurance', 'insurance'],
['shipping_discount', 'shippingDiscount'],
['discount', 'discount'],
];
}
}

View file

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class AmountTest extends TestCase
{
public function test() {
$money = \Mockery::mock(Money::class);
$money->expects('currencyCode')->andReturn('currencyCode');
$money->expects('value')->andReturn(1.10);
$testee = new Amount($money);
$this->assertEquals('currencyCode', $testee->currencyCode());
$this->assertEquals(1.10, $testee->value());
}
public function testBreakdownIsNull() {
$money = \Mockery::mock(Money::class);
$money->expects('currencyCode')->andReturn('currencyCode');
$money->expects('value')->andReturn(1.10);
$testee = new Amount($money);
$this->assertNull($testee->breakdown());
$expectedArray = [
'currency_code' => 'currencyCode',
'value' => 1.10,
];
$this->assertEquals($expectedArray, $testee->toArray());
}
public function testBreakdown() {
$money = \Mockery::mock(Money::class);
$money->expects('currencyCode')->andReturn('currencyCode');
$money->expects('value')->andReturn(1.10);
$breakdown = \Mockery::mock(AmountBreakdown::class);
$breakdown->expects('toArray')->andReturn([1]);
$testee = new Amount($money, $breakdown);
$this->assertEquals($breakdown, $testee->breakdown());
$expectedArray = [
'currency_code' => 'currencyCode',
'value' => 1.10,
'breakdown' => [1]
];
$this->assertEquals($expectedArray, $testee->toArray());
}
}

View file

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class ErrorResponseCollectionTest extends TestCase
{
public function testHasErrorCode() {
$error1 = \Mockery::mock(ErrorResponse::class);
$error1
->expects('code')
->times(3)
->andReturn('code-1');
$error2 = \Mockery::mock(ErrorResponse::class);
$error2
->expects('code')
->times(3)
->andReturn('code-2');
$testee = new ErrorResponseCollection($error1, $error2);
$this->assertTrue($testee->hasErrorCode('code-1'), 'code-1 should return true');
$this->assertTrue($testee->hasErrorCode('code-2'), 'code-2 should return true');
$this->assertFalse($testee->hasErrorCode('code-3'), 'code-3 should not return true');
}
public function testCodes() {
$error1 = \Mockery::mock(ErrorResponse::class);
$error1
->expects('code')
->andReturn('code-1');
$error2 = \Mockery::mock(ErrorResponse::class);
$error2
->expects('code')
->andReturn('code-2');
$testee = new ErrorResponseCollection($error1, $error2);
$expected = ['code-1', 'code-2'];
$this->assertEquals($expected, $testee->codes());
}
public function testErrors() {
$error1 = \Mockery::mock(ErrorResponse::class);
$error2 = \Mockery::mock(ErrorResponse::class);
$testee = new ErrorResponseCollection($error1, $error2);
$errors = $testee->errors();
$this->assertEquals(2, count($errors), 'Two errors stored.');
$this->assertEquals($error1, $errors[0]);
$this->assertEquals($error2, $errors[1]);
}
}

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class ErrorResponseTest extends TestCase
{
public function test() {
$testee = new ErrorResponse(
'code',
'message',
500,
'url',
[1,2,3]
);
$this->assertEquals('code', $testee->code());
$this->assertEquals('message', $testee->message());
$this->assertEquals(500, $testee->httpCode());
$this->assertEquals('url', $testee->url());
$this->assertEquals([1,2,3], $testee->args());
}
public function testIs() {
$testee = new ErrorResponse(
'code',
'message',
500,
'url',
[1,2,3]
);
$this->assertTrue($testee->is('code'));
$this->assertFalse($testee->is('not-code'));
}
}

View file

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class ItemTest extends TestCase
{
public function test() {
$unitAmount = \Mockery::mock(Money::class);
$tax = \Mockery::mock(Money::class);
$testee = new Item(
'name',
$unitAmount,
1,
'description',
$tax,
'sku',
'PHYSICAL_GOODS'
);
$this->assertEquals('name', $testee->name());
$this->assertEquals($unitAmount, $testee->unitAmount());
$this->assertEquals(1, $testee->quantity());
$this->assertEquals('description', $testee->description());
$this->assertEquals($tax, $testee->tax());
$this->assertEquals('sku', $testee->sku());
$this->assertEquals('PHYSICAL_GOODS', $testee->category());
}
public function testDigitalGoodsCategory() {
$unitAmount = \Mockery::mock(Money::class);
$tax = \Mockery::mock(Money::class);
$testee = new Item(
'name',
$unitAmount,
1,
'description',
$tax,
'sku',
'DIGITAL_GOODS'
);
$this->assertEquals('DIGITAL_GOODS', $testee->category());
}
public function testToArray() {
$unitAmount = \Mockery::mock(Money::class);
$unitAmount
->expects('toArray')
->andReturn([1]);
$tax = \Mockery::mock(Money::class);
$tax
->expects('toArray')
->andReturn([2]);
$testee = new Item(
'name',
$unitAmount,
1,
'description',
$tax,
'sku',
'PHYSICAL_GOODS'
);
$expected = [
'name' => 'name',
'unit_amount' => [1],
'quantity' => 1,
'description' => 'description',
'sku' => 'sku',
'category' => 'PHYSICAL_GOODS',
'tax' => [2],
];
$this->assertEquals($expected, $testee->toArray());
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class MoneyTest extends TestCase
{
public function test() {
$testee = new Money(1.10, 'currencyCode');
$this->assertEquals(1.10, $testee->value());
$this->assertEquals('currencyCode', $testee->currencyCode());
$expected = [
'currency_code' => 'currencyCode',
'value' => 1.10,
];
$this->assertEquals($expected, $testee->toArray());
}
}

View file

@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use PHPUnit\Framework\TestCase;
class PurchaseUnitTest extends TestCase
{
public function test() {
$amount = \Mockery::mock(Amount::class);
$amount->expects('breakdown')->andReturnNull();
$amount->expects('toArray')->andReturn(['amount']);
$item1 = \Mockery::mock(Item::class);
$item1->expects('toArray')->andReturn(['item1']);
$item2 = \Mockery::mock(Item::class);
$item2->expects('toArray')->andReturn(['item2']);
$shipping = \Mockery::mock(Shipping::class);
$shipping->expects('toArray')->andReturn(['shipping']);
$testee = new PurchaseUnit(
$amount,
[],
$shipping,
'referenceId',
'description',
null,
'customId',
'invoiceId',
'softDescriptor'
);
$this->assertEquals($amount, $testee->amount());
$this->assertEquals('referenceId', $testee->referenceId());
$this->assertEquals('description', $testee->description());
$this->assertNull($testee->payee());
$this->assertEquals('customId', $testee->customId());
$this->assertEquals('invoiceId', $testee->invoiceId());
$this->assertEquals('softDescriptor', $testee->softDescriptor());
$this->assertEquals($shipping, $testee->shipping());
$this->assertEquals([], $testee->items());
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'items' => [],
'shipping' => ['shipping'],
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
];
$this->assertEquals($expected, $testee->toArray());
}
public function testDontDitchBecauseOfBreakdown() {
$breakdown = \Mockery::mock(AmountBreakdown::class);
$breakdown->expects('shipping')->andReturnNull();
$breakdown->expects('itemTotal')->andReturnNull();
$breakdown->expects('discount')->andReturnNull();
$breakdown->expects('taxTotal')->andReturnNull();
$breakdown->expects('shippingDiscount')->andReturnNull();
$breakdown->expects('handling')->andReturnNull();
$breakdown->expects('insurance')->andReturnNull();
$amount = \Mockery::mock(Amount::class);
$amount->expects('breakdown')->andReturn($breakdown);
$amount->expects('value')->andReturn(0.0);
$amount->expects('toArray')->andReturn(['amount']);
$item1 = \Mockery::mock(Item::class);
$item1->expects('toArray')->andReturn(['item1']);
$item2 = \Mockery::mock(Item::class);
$item2->expects('toArray')->andReturn(['item2']);
$shipping = \Mockery::mock(Shipping::class);
$shipping->expects('toArray')->andReturn(['shipping']);
$testee = new PurchaseUnit(
$amount,
[],
$shipping,
'referenceId',
'description',
null,
'customId',
'invoiceId',
'softDescriptor'
);
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'shipping' => ['shipping'],
'custom_id' => 'customId',
'items' => [],
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
];
$this->assertEquals($expected, $testee->toArray());
}
public function testDitchBecauseOfBreakdown() {
$breakdown = \Mockery::mock(AmountBreakdown::class);
$breakdown->expects('shipping')->andReturnNull();
$breakdown->expects('itemTotal')->andReturnNull();
$breakdown->expects('discount')->andReturnNull();
$breakdown->expects('taxTotal')->andReturnNull();
$breakdown->expects('shippingDiscount')->andReturnNull();
$breakdown->expects('handling')->andReturnNull();
$breakdown->expects('insurance')->andReturnNull();
$amount = \Mockery::mock(Amount::class);
$amount->expects('breakdown')->andReturn($breakdown);
$amount->expects('value')->andReturn(1.00);
$amount->expects('toArray')->andReturn(['amount']);
$item1 = \Mockery::mock(Item::class);
$item1->expects('toArray')->andReturn(['item1']);
$item2 = \Mockery::mock(Item::class);
$item2->expects('toArray')->andReturn(['item2']);
$shipping = \Mockery::mock(Shipping::class);
$shipping->expects('toArray')->andReturn(['shipping']);
$testee = new PurchaseUnit(
$amount,
[],
$shipping,
'referenceId',
'description',
null,
'customId',
'invoiceId',
'softDescriptor'
);
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'shipping' => ['shipping'],
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
];
$this->assertEquals($expected, $testee->toArray());
}
public function testPayee() {
$amount = \Mockery::mock(Amount::class);
$amount->expects('breakdown')->andReturnNull();
$amount->expects('toArray')->andReturn(['amount']);
$item1 = \Mockery::mock(Item::class);
$item1->expects('toArray')->andReturn(['item1']);
$item2 = \Mockery::mock(Item::class);
$item2->expects('toArray')->andReturn(['item2']);
$shipping = \Mockery::mock(Shipping::class);
$shipping->expects('toArray')->andReturn(['shipping']);
$payee = \Mockery::mock(Payee::class);
$payee->expects('toArray')->andReturn(['payee']);
$testee = new PurchaseUnit(
$amount,
[],
$shipping,
'referenceId',
'description',
$payee,
'customId',
'invoiceId',
'softDescriptor'
);
$this->assertEquals($payee, $testee->payee());
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'items' => [],
'shipping' => ['shipping'],
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
'payee' => ['payee'],
];
$this->assertEquals($expected, $testee->toArray());
}
}

View file

@ -0,0 +1,5 @@
{
"plugins": [
"babel-plugin-transform-object-rest-spread"
]
}

View file

@ -1542,6 +1542,29 @@
"object.assign": "^4.1.0"
}
},
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U="
},
"babel-plugin-transform-object-rest-spread": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
"integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=",
"requires": {
"babel-plugin-syntax-object-rest-spread": "^6.8.0",
"babel-runtime": "^6.26.0"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -2123,6 +2146,11 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"core-js": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
},
"core-js-compat": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz",
@ -5282,6 +5310,11 @@
"regenerate": "^1.4.0"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",

View file

@ -12,12 +12,12 @@
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.1.2"
"webpack-cli": "^3.1.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0"
},
"scripts": {
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
"dev": "cross-env BABEL_ENV=default webpack --watch"
},
"dependencies": {}
}
}

View file

@ -6,10 +6,15 @@ namespace Inpsyde\PayPalCommerce\WcGateway;
use Dhii\Data\Container\ContainerInterface;
use Inpsyde\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGatewayBase;
use Inpsyde\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsFields;
return [
'wcgateway.gateway.base' => function (ContainerInterface $container) : WcGatewayBase {
return new WcGatewayBase();
},
'wcgateway.gateway' => function (ContainerInterface $container) : WcGateway {
$sessionHandler = $container->get('session.handler');
$cartRepository = $container->get('api.cart-repository');
@ -23,10 +28,14 @@ return [
return new DisableGateways($sessionHandler);
},
'wcgateway.settings' => function (ContainerInterface $container) : Settings {
$gateway = $container->get('wcgateway.gateway');
$gateway = $container->get('wcgateway.gateway.base');
$settingsField = $container->get('wcgateway.settings.fields');
return new Settings($gateway, $settingsField);
},
'wcgateway.notice.connect' => function (ContainerInterface $container) : ConnectAdminNotice {
$settings = $container->get('wcgateway.settings');
return new ConnectAdminNotice($settings);
},
'wcgateway.settings.fields' => function (ContainerInterface $container) : SettingsFields {
return new SettingsFields();
},

View file

@ -15,11 +15,8 @@ use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsFields;
//phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
class WcGateway extends \WC_Payment_Gateway
class WcGateway extends WcGatewayBase implements WcGatewayInterface
{
const ID = 'ppcp-gateway';
private $isSandbox = true;
private $sessionHandler;
private $endpoint;
@ -40,7 +37,6 @@ class WcGateway extends \WC_Payment_Gateway
$this->endpoint = $endpoint;
$this->orderFactory = $orderFactory;
$this->settingsFields = $settingsFields;
$this->id = self::ID;
$this->method_title = __('PayPal Payments', 'woocommerce-paypal-gateway');
$this->method_description = __(
@ -61,6 +57,8 @@ class WcGateway extends \WC_Payment_Gateway
'process_admin_options',
]
);
parent::__construct();
}
public function init_form_fields()

View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Gateway;
use WC_Payment_Gateway;
class WcGatewayBase extends WC_Payment_Gateway implements WcGatewayInterface
{
const ID = 'ppcp-gateway';
public function __construct()
{
$this->id = self::ID;
}
}

View file

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Gateway;
interface WcGatewayInterface
{
}

View file

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Notice;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
class ConnectAdminNotice
{
private $settings;
public function __construct(Settings $settings)
{
$this->settings = $settings;
}
public function display()
{
if (!$this->shouldDisplay()) {
return;
}
echo sprintf(
'<div class="notice notice-warning"><p>%s</p></div>',
wp_kses_post(
sprintf(
/* translators: %1$s the gateway name */
__(
'%1$s is almost ready. To get started, <a href="%2$s">connect your account</a>.',
'woocommerce-paypal-commerce-gateway'
),
$this->settings->get('title'),
// TODO: find a better way to get the url
admin_url('admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway')
)
)
);
}
protected function shouldDisplay(): bool
{
// TODO: decide on what condition to display
return !wc_string_to_bool($this->settings->get('enabled'));
}
}

View file

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Settings;
use Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGatewayInterface;
use Psr\Container\ContainerInterface;
class Settings implements ContainerInterface
@ -13,7 +13,7 @@ class Settings implements ContainerInterface
private $gateway;
private $formFields;
public function __construct(WcGateway $gateway, SettingsFields $formFields)
public function __construct(WcGatewayInterface $gateway, SettingsFields $formFields)
{
$this->gateway = $gateway;
$this->formFields = $formFields;

View file

@ -7,6 +7,7 @@ use Dhii\Container\ServiceProvider;
use Dhii\Modular\Module\ModuleInterface;
use Inpsyde\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGateway;
use Inpsyde\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
@ -43,6 +44,17 @@ class WcGatewayModule implements ModuleInterface
}
);
add_action(
'admin_notices',
function () use ($container) : void {
$notice = $container->get('wcgateway.notice.connect');
/**
* @var ConnectAdminNotice $notice
*/
$notice->display();
}
);
add_filter(
'woocommerce_order_actions',
function ($orderActions) : array {