add api client to main repository

This commit is contained in:
David Remer 2020-08-31 13:38:54 +03:00
parent 7c5638764e
commit f5bb4048cd
100 changed files with 10765 additions and 2 deletions

View file

@ -29,4 +29,4 @@ jobs:
- name: Run test suite
run: ./vendor/bin/phpunit
- name: Run cs
run: ./vendor/bin/phpcs src inc modules.local/ppcp-wc-gateway/ modules.local/ppcp-webhooks/ modules.local/ppcp-button/ modules.local/ppcp-onboarding/ modules.local/ppcp-subscription --extensions=php
run: ./vendor/bin/phpcs src inc modules.local --extensions=php

View file

@ -24,7 +24,6 @@
"dhii/module-interface": "0.2.x-dev",
"psr/container": "^1.0",
"inpsyde/ppcp-admin-notices": "dev-master",
"inpsyde/ppcp-api-client": "dev-master",
"inpsyde/ppcp-session": "dev-master",
"oomphinc/composer-installers-extender": "^1.1",
"container-interop/service-provider": "^0.4.0",

View file

@ -0,0 +1,3 @@
vendor/
build/
composer.lock

View file

@ -0,0 +1,3 @@
# PPCP API Client
This module takes care of the api client for the woocommerce-paypal-commerce-gateway.

View file

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient;
use Dhii\Data\Container\ContainerInterface;
return [
];

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient;
use Dhii\Modular\Module\ModuleInterface;
return function (): ModuleInterface {
return new ApiModule();
};

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<ruleset name="WooCommerce Coding Standards">
<description>My projects ruleset.</description>
<!-- Configs -->
<config name="minimum_supported_wp_version" value="4.7" />
<config name="testVersion" value="7.2-" />
<!-- Rules -->
<rule ref="WooCommerce-Core" />
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array" value="woocommerce-paypal-commerce-gateway" />
</properties>
</rule>
<rule ref="PHPCompatibility">
<exclude-pattern>tests/</exclude-pattern>
</rule>
</ruleset>

View file

@ -0,0 +1,270 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient;
use Dhii\Data\Container\ContainerInterface;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\IdentityToken;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AddressFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AmountFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ApplicationContextFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ItemFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PayeeFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PayerFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PaymentsFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PaymentSourceFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ShippingFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use Inpsyde\PayPalCommerce\ApiClient\Helper\DccApplies;
use Inpsyde\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Inpsyde\PayPalCommerce\Onboarding\Environment;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
use WpOop\TransientCache\CachePoolFactory;
return [
'api.host' => function(ContainerInterface $container) : string {
return 'https://api.paypal.com';
},
'api.paypal-host' => function(ContainerInterface $container) : string {
return 'https://api.paypal.com';
},
'api.partner_merchant_id' => static function () : string {
return '';
},
'api.merchant_email' => function () : string {
return '';
},
'api.merchant_id' => function () : string {
return '';
},
'api.key' => static function (): string {
return '';
},
'api.secret' => static function (): string {
return '';
},
'api.prefix' => static function (): string {
return 'WC-';
},
'api.bearer' => static function (ContainerInterface $container): Bearer {
global $wpdb;
$cacheFactory = new CachePoolFactory($wpdb);
$pool = $cacheFactory->createCachePool('ppcp-token');
$key = $container->get('api.key');
$secret = $container->get('api.secret');
$host = $container->get('api.host');
$logger = $container->get('woocommerce.logger.woocommerce');
return new PayPalBearer(
$pool,
$host,
$key,
$secret,
$logger
);
},
'api.endpoint.payment-token' => static function (ContainerInterface $container) : PaymentTokenEndpoint {
return new PaymentTokenEndpoint(
$container->get('api.host'),
$container->get('api.bearer'),
$container->get('api.factory.payment-token'),
$container->get('woocommerce.logger.woocommerce'),
$container->get('api.prefix')
);
},
'api.endpoint.webhook' => static function (ContainerInterface $container) : WebhookEndpoint {
return new WebhookEndpoint(
$container->get('api.host'),
$container->get('api.bearer'),
$container->get('api.factory.webhook'),
$container->get('woocommerce.logger.woocommerce')
);
},
'api.endpoint.partner-referrals' => static function (ContainerInterface $container) : PartnerReferrals {
return new PartnerReferrals(
$container->get('api.host'),
$container->get('api.bearer'),
$container->get('api.repository.partner-referrals-data'),
$container->get('woocommerce.logger.woocommerce')
);
},
'api.endpoint.identity-token' => static function (ContainerInterface $container) : IdentityToken {
$logger = $container->get('woocommerce.logger.woocommerce');
$prefix = $container->get('api.prefix');
return new IdentityToken(
$container->get('api.host'),
$container->get('api.bearer'),
$logger,
$prefix
);
},
'api.endpoint.payments' => static function (ContainerInterface $container): PaymentsEndpoint {
$authorizationFactory = $container->get('api.factory.authorization');
$logger = $container->get('woocommerce.logger.woocommerce');
return new PaymentsEndpoint(
$container->get('api.host'),
$container->get('api.bearer'),
$authorizationFactory,
$logger
);
},
'api.endpoint.login-seller' => static function (ContainerInterface $container) : LoginSeller {
$logger = $container->get('woocommerce.logger.woocommerce');
return new LoginSeller(
$container->get('api.paypal-host'),
$container->get('api.partner_merchant_id'),
$logger
);
},
'api.endpoint.order' => static function (ContainerInterface $container): OrderEndpoint {
$orderFactory = $container->get('api.factory.order');
$patchCollectionFactory = $container->get('api.factory.patch-collection-factory');
$logger = $container->get('woocommerce.logger.woocommerce');
/**
* @var Settings $settings
*/
$settings = $container->get('wcgateway.settings');
$intent = $settings->has('intent') && strtoupper((string) $settings->get('intent')) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$applicationContextRepository = $container->get('api.repository.application-context');
$paypalRequestId = $container->get('api.repository.paypal-request-id');
return new OrderEndpoint(
$container->get('api.host'),
$container->get('api.bearer'),
$orderFactory,
$patchCollectionFactory,
$intent,
$logger,
$applicationContextRepository,
$paypalRequestId
);
},
'api.repository.paypal-request-id' => static function(ContainerInterface $container) : PayPalRequestIdRepository {
return new PayPalRequestIdRepository();
},
'api.repository.application-context' => static function(ContainerInterface $container) : ApplicationContextRepository {
$settings = $container->get('wcgateway.settings');
return new ApplicationContextRepository($settings);
},
'api.repository.partner-referrals-data' => static function (ContainerInterface $container) : PartnerReferralsData {
$merchantEmail = $container->get('api.merchant_email');
$dccApplies = $container->get('api.helpers.dccapplies');
return new PartnerReferralsData($merchantEmail, $dccApplies);
},
'api.repository.cart' => static function (ContainerInterface $container): CartRepository {
$factory = $container->get('api.factory.purchase-unit');
return new CartRepository($factory);
},
'api.repository.payee' => static function (ContainerInterface $container): PayeeRepository {
$merchantEmail = $container->get('api.merchant_email');
$merchantId = $container->get('api.merchant_id');
return new PayeeRepository($merchantEmail, $merchantId);
},
'api.factory.application-context' => static function (ContainerInterface $container) : ApplicationContextFactory {
return new ApplicationContextFactory();
},
'api.factory.payment-token' => static function (ContainerInterface $container) : PaymentTokenFactory {
return new PaymentTokenFactory();
},
'api.factory.webhook' => static function (ContainerInterface $container): WebhookFactory {
return new WebhookFactory();
},
'api.factory.purchase-unit' => static function (ContainerInterface $container): PurchaseUnitFactory {
$amountFactory = $container->get('api.factory.amount');
$payeeRepository = $container->get('api.repository.payee');
$payeeFactory = $container->get('api.factory.payee');
$itemFactory = $container->get('api.factory.item');
$shippingFactory = $container->get('api.factory.shipping');
$paymentsFactory = $container->get('api.factory.payments');
$prefix = $container->get('api.prefix');
return new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory,
$prefix
);
},
'api.factory.patch-collection-factory' => static function (ContainerInterface $container): PatchCollectionFactory {
return new PatchCollectionFactory();
},
'api.factory.payee' => static function (ContainerInterface $container): PayeeFactory {
return new PayeeFactory();
},
'api.factory.item' => static function (ContainerInterface $container): ItemFactory {
return new ItemFactory();
},
'api.factory.shipping' => static function (ContainerInterface $container): ShippingFactory {
$addressFactory = $container->get('api.factory.address');
return new ShippingFactory($addressFactory);
},
'api.factory.amount' => static function (ContainerInterface $container): AmountFactory {
$itemFactory = $container->get('api.factory.item');
return new AmountFactory($itemFactory);
},
'api.factory.payer' => static function (ContainerInterface $container): PayerFactory {
$addressFactory = $container->get('api.factory.address');
return new PayerFactory($addressFactory);
},
'api.factory.address' => static function (ContainerInterface $container): AddressFactory {
return new AddressFactory();
},
'api.factory.payment-source' => static function (ContainerInterface $container): PaymentSourceFactory {
return new PaymentSourceFactory();
},
'api.factory.order' => static function (ContainerInterface $container): OrderFactory {
$purchaseUnitFactory = $container->get('api.factory.purchase-unit');
$payerFactory = $container->get('api.factory.payer');
$applicationContextRepository = $container->get('api.repository.application-context');
$applicationContextFactory = $container->get('api.factory.application-context');
$paymentSourceFactory = $container->get('api.factory.payment-source');
return new OrderFactory(
$purchaseUnitFactory,
$payerFactory,
$applicationContextRepository,
$applicationContextFactory,
$paymentSourceFactory
);
},
'api.factory.payments' => static function (ContainerInterface $container): PaymentsFactory {
$authorizationFactory = $container->get('api.factory.authorization');
return new PaymentsFactory($authorizationFactory);
},
'api.factory.authorization' => static function (ContainerInterface $container): AuthorizationFactory {
return new AuthorizationFactory();
},
'api.helpers.dccapplies' => static function (ContainerInterface $container) : DccApplies {
return new DccApplies();
},
];

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient;
use Dhii\Container\ServiceProvider;
use Dhii\Modular\Module\Exception\ModuleExceptionInterface;
use Dhii\Modular\Module\ModuleInterface;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
class ApiModule implements ModuleInterface
{
public function setup(): ServiceProviderInterface
{
return new ServiceProvider(
require __DIR__ . '/../services.php',
require __DIR__ . '/../extensions.php'
);
}
public function run(ContainerInterface $container)
{
// TODO: Implement run() method.
}
}

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Authentication;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Token;
interface Bearer
{
public function bearer(): Token;
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Authentication;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Token;
class ConnectBearer implements Bearer
{
public function bearer(): Token
{
$data = (object) [
'created' => time(),
'expires_in' => 3600,
'token' => 'token',
];
return new Token($data);
}
}

View file

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Authentication;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\RequestTrait;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Token;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
class PayPalBearer implements Bearer
{
use RequestTrait;
public const CACHE_KEY = 'ppcp-bearer';
private $cache;
private $host;
private $key;
private $secret;
private $logger;
public function __construct(
CacheInterface $cache,
string $host,
string $key,
string $secret,
LoggerInterface $logger
) {
$this->cache = $cache;
$this->host = $host;
$this->key = $key;
$this->secret = $secret;
$this->logger = $logger;
}
public function bearer(): Token
{
try {
$bearer = Token::fromJson((string) $this->cache->get(self::CACHE_KEY));
return ($bearer->isValid()) ? $bearer : $this->newBearer();
} catch (RuntimeException $error) {
return $this->newBearer();
}
}
private function newBearer(): Token
{
$url = trailingslashit($this->host) . 'v1/oauth2/token?grant_type=client_credentials';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Basic ' . base64_encode($this->key . ':' . $this->secret),
],
];
$response = $this->request(
$url,
$args
);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
$error = new RuntimeException(
__('Could not create token.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$token = Token::fromJson($response['body']);
$this->cache->set(self::CACHE_KEY, $token->asJson());
return $token;
}
}

View file

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Token;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
class IdentityToken
{
use RequestTrait;
private $bearer;
private $host;
private $logger;
private $prefix;
public function __construct(string $host, Bearer $bearer, LoggerInterface $logger, string $prefix)
{
$this->host = $host;
$this->bearer = $bearer;
$this->logger = $logger;
$this->prefix = $prefix;
}
public function generateForCustomer(int $customerId): Token
{
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v1/identity/generate-token';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
],
];
if ($customerId && defined('PPCP_FLAG_SUBSCRIPTION') && PPCP_FLAG_SUBSCRIPTION) {
$args['body'] = json_encode(['customer_id' => $this->prefix . $customerId]);
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__(
'Could not create identity token.',
'woocommerce-paypal-commerce-gateway'
)
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 200) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$token = Token::fromJson($response['body']);
return $token;
}
}

View file

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Psr\Log\LoggerInterface;
class LoginSeller
{
use RequestTrait;
private $host;
private $partnerMerchantId;
private $logger;
public function __construct(
string $host,
string $partnerMerchantId,
LoggerInterface $logger
) {
$this->host = $host;
$this->partnerMerchantId = $partnerMerchantId;
$this->logger = $logger;
}
public function credentialsFor(
string $sharedId,
string $authCode,
string $sellerNonce
): \stdClass {
$token = $this->generateTokenFor($sharedId, $authCode, $sellerNonce);
$url = trailingslashit($this->host) .
'v1/customer/partners/' . $this->partnerMerchantId .
'/merchant-integrations/credentials/';
$args = [
'method' => 'GET',
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
],
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not fetch credentials.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if (! isset($json->client_id) || ! isset($json->client_secret)) {
$error = isset($json->details) ?
new PayPalApiException(
$json,
$statusCode
) : new RuntimeException(
__('Credentials not found.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
return $json;
}
private function generateTokenFor(
string $sharedId,
string $authCode,
string $sellerNonce
): string {
$url = trailingslashit($this->host) . 'v1/oauth2/token/';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Basic ' . base64_encode($sharedId . ':'),
],
'body' => [
'grant_type' => 'authorization_code',
'code' => $authCode,
'code_verifier' => $sellerNonce,
],
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not create token.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if (! isset($json->access_token)) {
$error = isset($json->details) ?
new PayPalApiException(
$json,
$statusCode
) : new RuntimeException(
__('No token found.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
return (string) $json->access_token;
}
}

View file

@ -0,0 +1,418 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentToken;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ApplicationContextFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\Helper\ErrorResponse;
use Inpsyde\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Psr\Log\LoggerInterface;
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
class OrderEndpoint
{
use RequestTrait;
private $host;
private $bearer;
private $orderFactory;
private $patchCollectionFactory;
private $intent;
private $logger;
private $applicationContextRepository;
private $bnCode;
private $payPalRequestIdRepository;
public function __construct(
string $host,
Bearer $bearer,
OrderFactory $orderFactory,
PatchCollectionFactory $patchCollectionFactory,
string $intent,
LoggerInterface $logger,
ApplicationContextRepository $applicationContextRepository,
PayPalRequestIdRepository $payPalRequestIdRepository,
string $bnCode = ''
) {
$this->host = $host;
$this->bearer = $bearer;
$this->orderFactory = $orderFactory;
$this->patchCollectionFactory = $patchCollectionFactory;
$this->intent = $intent;
$this->logger = $logger;
$this->applicationContextRepository = $applicationContextRepository;
$this->bnCode = $bnCode;
$this->payPalRequestIdRepository = $payPalRequestIdRepository;
}
public function withBnCode(string $bnCode): OrderEndpoint
{
$this->bnCode = $bnCode;
return $this;
}
/**
* @param PurchaseUnit[] $items
*/
public function createForPurchaseUnits(
array $items,
Payer $payer = null,
PaymentToken $paymentToken = null,
PaymentMethod $paymentMethod = null,
string $paypalRequestId = ''
): Order {
$containsPhysicalGoods = false;
$items = array_filter(
$items,
//phpcs:ignore Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
static function ($item) use (&$containsPhysicalGoods): bool {
$isPurchaseUnit = is_a($item, PurchaseUnit::class);
if ($isPurchaseUnit && $item->containsPhysicalGoodsItems()) {
$containsPhysicalGoods = true;
}
return $isPurchaseUnit;
}
);
$shippingPreference = $containsPhysicalGoods
? ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE
: ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING;
$bearer = $this->bearer->bearer();
$data = [
'intent' => $this->intent,
'purchase_units' => array_map(
static function (PurchaseUnit $item): array {
return $item->toArray();
},
$items
),
'application_context' => $this->applicationContextRepository
->currentContext($shippingPreference)->toArray(),
];
if ($payer) {
$data['payer'] = $payer->toArray();
}
if ($paymentToken) {
$data['payment_source']['token'] = $paymentToken->toArray();
}
if ($paymentMethod) {
$data['payment_method'] = $paymentMethod->toArray();
}
$url = trailingslashit($this->host) . 'v2/checkout/orders';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
],
'body' => json_encode($data),
];
$paypalRequestId = $paypalRequestId ? $paypalRequestId : uniqid('ppcp-', true);
$args['headers']['PayPal-Request-Id'] = $paypalRequestId;
if ($this->bnCode) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bnCode;
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not create order.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$order = $this->orderFactory->fromPayPalResponse($json);
$this->payPalRequestIdRepository->setForOrder($order, $paypalRequestId);
return $order;
}
public function capture(Order $order): Order
{
if ($order->status()->is(OrderStatus::COMPLETED)) {
return $order;
}
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/checkout/orders/' . $order->id() . '/capture';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
'PayPal-Request-Id' => $this->payPalRequestIdRepository->getForOrder($order),
],
];
if ($this->bnCode) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bnCode;
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not capture order.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
$error = new PayPalApiException(
$json,
$statusCode
);
// If the order has already been captured, we return the updated order.
if (strpos($response['body'], ErrorResponse::ORDER_ALREADY_CAPTURED) !== false) {
return $this->order($order->id());
}
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$order = $this->orderFactory->fromPayPalResponse($json);
return $order;
}
public function authorize(Order $order): Order
{
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/checkout/orders/' . $order->id() . '/authorize';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
'PayPal-Request-Id' => $this->payPalRequestIdRepository->getForOrder($order),
],
];
if ($this->bnCode) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bnCode;
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__(
'Could not authorize order.',
'woocommerce-paypal-commerce-gateway'
)
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
if (strpos($response['body'], ErrorResponse::ORDER_ALREADY_AUTHORIZED) !== false) {
return $this->order($order->id());
}
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$order = $this->orderFactory->fromPayPalResponse($json);
return $order;
}
public function order(string $id): Order
{
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/checkout/orders/' . $id;
$args = [
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'PayPal-Request-Id' => $this->payPalRequestIdRepository->getForOrderId($id),
],
];
if ($this->bnCode) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bnCode;
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode === 404 || empty($response['body'])) {
$error = new RuntimeException(
__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway'),
404
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
if ($statusCode !== 200) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$order = $this->orderFactory->fromPayPalResponse($json);
return $order;
}
public function patchOrderWith(Order $orderToUpdate, Order $orderToCompare): Order
{
$patches = $this->patchCollectionFactory->fromOrders($orderToUpdate, $orderToCompare);
if (! count($patches->patches())) {
return $orderToUpdate;
}
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/checkout/orders/' . $orderToUpdate->id();
$args = [
'method' => 'PATCH',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
'PayPal-Request-Id' => $this->payPalRequestIdRepository->getForOrder(
$orderToUpdate
),
],
'body' => json_encode($patches->toArray()),
];
if ($this->bnCode) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bnCode;
}
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not retrieve order.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 204) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$newOrder = $this->order($orderToUpdate->id());
return $newOrder;
}
}

View file

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use Psr\Log\LoggerInterface;
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
class PartnerReferrals
{
use RequestTrait;
private $host;
private $bearer;
private $data;
private $logger;
public function __construct(
string $host,
Bearer $bearer,
PartnerReferralsData $data,
LoggerInterface $logger
) {
$this->host = $host;
$this->bearer = $bearer;
$this->data = $data;
$this->logger = $logger;
}
public function signupLink(): string
{
$data = $this->data->data();
$bearer = $this->bearer->bearer();
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
],
'body' => json_encode($data),
];
$url = trailingslashit($this->host) . 'v2/customer/partner-referrals';
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not create referral.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
foreach ($json->links as $link) {
if ($link->rel === 'action_url') {
return (string) $link->href;
}
}
$error = new RuntimeException(
__('Action URL not found.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
}

View file

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentToken;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
use Psr\Log\LoggerInterface;
class PaymentTokenEndpoint
{
use RequestTrait;
private $bearer;
private $host;
private $factory;
private $logger;
private $prefix;
public function __construct(
string $host,
Bearer $bearer,
PaymentTokenFactory $factory,
LoggerInterface $logger,
string $prefix
) {
$this->host = $host;
$this->bearer = $bearer;
$this->factory = $factory;
$this->logger = $logger;
$this->prefix = $prefix;
}
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
/**
* @param int $id
* @return PaymentToken[]
*/
public function forUser(int $id): array
{
$bearer = $this->bearer->bearer();
$customerId = $this->prefix . $id;
$url = trailingslashit($this->host) . 'v2/vault/payment-tokens/?customer_id=' . $customerId;
$args = [
'method' => 'GET',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
],
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not fetch payment token.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 200) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$tokens = [];
foreach ($json->payment_tokens as $tokenValue) {
$tokens[] = $this->factory->fromPayPalResponse($tokenValue);
}
if (empty($tokens)) {
$error = new RuntimeException(
sprintf(
// translators: %d is the customer id.
__('No token stored for customer %d.', 'woocommerce-paypal-commerce-gateway'),
$id
)
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
return $tokens;
}
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
public function deleteToken(PaymentToken $token): bool
{
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/vault/payment-tokens/' . $token->id();
$args = [
'method' => 'DELETE',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
],
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not delete payment token.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
return wp_remote_retrieve_response_code($response) === 204;
}
}

View file

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Psr\Log\LoggerInterface;
class PaymentsEndpoint
{
use RequestTrait;
private $host;
private $bearer;
private $authorizationFactory;
private $logger;
public function __construct(
string $host,
Bearer $bearer,
AuthorizationFactory $authorizationsFactory,
LoggerInterface $logger
) {
$this->host = $host;
$this->bearer = $bearer;
$this->authorizationFactory = $authorizationsFactory;
$this->logger = $logger;
}
public function authorization(string $authorizationId): Authorization
{
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v2/payments/authorizations/' . $authorizationId;
$args = [
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
],
];
$response = $this->request($url, $args);
$json = json_decode($response['body']);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not get authorized payment info.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 200) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$authorization = $this->authorizationFactory->fromPayPalRequest($json);
return $authorization;
}
public function capture(string $authorizationId): Authorization
{
$bearer = $this->bearer->bearer();
//phpcs:ignore Inpsyde.CodeQuality.LineLength.TooLong
$url = trailingslashit($this->host) . 'v2/payments/authorizations/' . $authorizationId . '/capture';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
],
];
$response = $this->request($url, $args);
$json = json_decode($response['body']);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Could not capture authorized payment.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$authorization = $this->authorizationFactory->fromPayPalRequest($json);
return $authorization;
}
}

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
trait RequestTrait
{
//phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
private function request(string $url, array $args)
{
/**
* This filter can be used to alter the request args.
* For example, during testing, the PayPal-Mock-Response header could be
* added here.
*/
$args = apply_filters('ppcp-request-args', $args, $url);
if (! isset($args['headers']['PayPal-Partner-Attribution-Id'])) {
$args['headers']['PayPal-Partner-Attribution-Id'] = 'Woo_PPCP';
}
return wp_remote_get($url, $args);
}
}

View file

@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Webhook;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use Psr\Log\LoggerInterface;
class WebhookEndpoint
{
use RequestTrait;
private $host;
private $bearer;
private $webhookFactory;
private $logger;
public function __construct(
string $host,
Bearer $bearer,
WebhookFactory $webhookFactory,
LoggerInterface $logger
) {
$this->host = $host;
$this->bearer = $bearer;
$this->webhookFactory = $webhookFactory;
$this->logger = $logger;
}
public function create(Webhook $hook): Webhook
{
/**
* An hook, which has an ID has already been created.
*/
if ($hook->id()) {
return $hook;
}
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v1/notifications/webhooks';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
],
'body' => json_encode($hook->toArray()),
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Not able to create a webhook.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$json = json_decode($response['body']);
$statusCode = (int) wp_remote_retrieve_response_code($response);
if ($statusCode !== 201) {
$error = new PayPalApiException(
$json,
$statusCode
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
$hook = $this->webhookFactory->fromPayPalResponse($json);
return $hook;
}
public function delete(Webhook $hook): bool
{
if (! $hook->id()) {
return false;
}
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v1/notifications/webhooks/' . $hook->id();
$args = [
'method' => 'DELETE',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
],
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Not able to delete the webhook.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
[
'args' => $args,
'response' => $response,
]
);
throw $error;
}
return wp_remote_retrieve_response_code($response) === 204;
}
public function verifyEvent(
string $authAlgo,
string $certUrl,
string $transmissionId,
string $transmissionSig,
string $transmissionTime,
string $webhookId,
\stdClass $webhookEvent
): bool {
$bearer = $this->bearer->bearer();
$url = trailingslashit($this->host) . 'v1/notifications/verify-webhook-signature';
$args = [
'method' => 'POST',
'headers' => [
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
],
'body' => json_encode(
[
'transmission_id' => $transmissionId,
'transmission_time' => $transmissionTime,
'cert_url' => $certUrl,
'auth_algo' => $authAlgo,
'transmission_sig' => $transmissionSig,
'webhook_id' => $webhookId,
'webhook_event' => $webhookEvent,
]
),
];
$response = $this->request($url, $args);
if (is_wp_error($response)) {
$error = new RuntimeException(
__('Not able to verify webhook event.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log(
'warning',
$error->getMessage(),
['args' => $args, 'response' => $response]
);
throw $error;
}
$json = json_decode($response['body']);
return isset($json->verification_status) && $json->verification_status === "SUCCESS";
}
public function verifyCurrentRequestForWebhook(Webhook $webhook): bool
{
if (! $webhook->id()) {
$error = new RuntimeException(
__('Not a valid webhook to verify.', 'woocommerce-paypal-commerce-gateway')
);
$this->logger->log('warning', $error->getMessage(), ['webhook' => $webhook]);
throw $error;
}
$expectedHeaders = [
'PAYPAL-AUTH-ALGO' => '',
'PAYPAL-CERT-URL' => '',
'PAYPAL-TRANSMISSION-ID' => '',
'PAYPAL-TRANSMISSION-SIG' => '',
'PAYPAL-TRANSMISSION-TIME' => '',
];
$headers = getallheaders();
foreach ($headers as $key => $header) {
$key = strtoupper($key);
if (isset($expectedHeaders[$key])) {
$expectedHeaders[$key] = $header;
}
};
foreach ($expectedHeaders as $key => $value) {
if (! empty($value)) {
continue;
}
$error = new RuntimeException(
sprintf(
// translators: %s is the headers key.
__(
'Not a valid webhook event. Header %s is missing',
'woocommerce-paypal-commerce-gateway'
),
$key
)
);
$this->logger->log('warning', $error->getMessage(), ['webhook' => $webhook]);
throw $error;
}
$requestBody = json_decode(file_get_contents("php://input"));
return $this->verifyEvent(
$expectedHeaders['PAYPAL-AUTH-ALGO'],
$expectedHeaders['PAYPAL-CERT-URL'],
$expectedHeaders['PAYPAL-TRANSMISSION-ID'],
$expectedHeaders['PAYPAL-TRANSMISSION-SIG'],
$expectedHeaders['PAYPAL-TRANSMISSION-TIME'],
$webhook->id(),
$requestBody ? $requestBody : new \stdClass()
);
}
}

View file

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Address
{
private $countryCode;
private $addressLine1;
private $addressLine2;
private $adminArea1;
private $adminArea2;
private $postalCode;
public function __construct(
string $countryCode,
string $addressLine1 = '',
string $addressLine2 = '',
string $adminArea1 = '',
string $adminArea2 = '',
string $postalCode = ''
) {
$this->countryCode = $countryCode;
$this->addressLine1 = $addressLine1;
$this->addressLine2 = $addressLine2;
$this->adminArea1 = $adminArea1;
$this->adminArea2 = $adminArea2;
$this->postalCode = $postalCode;
}
public function countryCode(): string
{
return $this->countryCode;
}
public function addressLine1(): string
{
return $this->addressLine1;
}
public function addressLine2(): string
{
return $this->addressLine2;
}
public function adminArea1(): string
{
return $this->adminArea1;
}
public function adminArea2(): string
{
return $this->adminArea2;
}
public function postalCode(): string
{
return $this->postalCode;
}
public function toArray(): array
{
return [
'country_code' => $this->countryCode(),
'address_line_1' => $this->addressLine1(),
'address_line_2' => $this->addressLine2(),
'admin_area_1' => $this->adminArea1(),
'admin_area_2' => $this->adminArea2(),
'postal_code' => $this->postalCode(),
];
}
}

View file

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Amount
{
private $money;
private $breakdown;
public function __construct(Money $money, AmountBreakdown $breakdown = null)
{
$this->money = $money;
$this->breakdown = $breakdown;
}
public function currencyCode(): string
{
return $this->money->currencyCode();
}
public function value(): float
{
return $this->money->value();
}
public function breakdown(): ?AmountBreakdown
{
return $this->breakdown;
}
public function toArray(): array
{
$amount = [
'currency_code' => $this->currencyCode(),
'value' => $this->value(),
];
if ($this->breakdown() && count($this->breakdown()->toArray())) {
$amount['breakdown'] = $this->breakdown()->toArray();
}
return $amount;
}
}

View file

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class AmountBreakdown
{
private $itemTotal;
private $shipping;
private $taxTotal;
private $handling;
private $insurance;
private $shippingDiscount;
private $discount;
public function __construct(
Money $itemTotal = null,
Money $shipping = null,
Money $taxTotal = null,
Money $handling = null,
Money $insurance = null,
Money $shippingDiscount = null,
Money $discount = null
) {
$this->itemTotal = $itemTotal;
$this->shipping = $shipping;
$this->taxTotal = $taxTotal;
$this->handling = $handling;
$this->insurance = $insurance;
$this->shippingDiscount = $shippingDiscount;
$this->discount = $discount;
}
public function itemTotal(): ?Money
{
return $this->itemTotal;
}
public function shipping(): ?Money
{
return $this->shipping;
}
public function taxTotal(): ?Money
{
return $this->taxTotal;
}
public function handling(): ?Money
{
return $this->handling;
}
public function insurance(): ?Money
{
return $this->insurance;
}
public function shippingDiscount(): ?Money
{
return $this->shippingDiscount;
}
public function discount(): ?Money
{
return $this->discount;
}
public function toArray(): array
{
$breakdown = [];
if ($this->itemTotal) {
$breakdown['item_total'] = $this->itemTotal->toArray();
}
if ($this->shipping) {
$breakdown['shipping'] = $this->shipping->toArray();
}
if ($this->taxTotal) {
$breakdown['tax_total'] = $this->taxTotal->toArray();
}
if ($this->handling) {
$breakdown['handling'] = $this->handling->toArray();
}
if ($this->insurance) {
$breakdown['insurance'] = $this->insurance->toArray();
}
if ($this->shippingDiscount) {
$breakdown['shipping_discount'] = $this->shippingDiscount->toArray();
}
if ($this->discount) {
$breakdown['discount'] = $this->discount->toArray();
}
return $breakdown;
}
}

View file

@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class ApplicationContext
{
public const LANDING_PAGE_LOGIN = 'LOGIN';
public const LANDING_PAGE_BILLING = 'BILLING';
public const LANDING_PAGE_NO_PREFERENCE = 'NO_PREFERENCE';
private const VALID_LANDING_PAGE_VALUES = [
self::LANDING_PAGE_LOGIN,
self::LANDING_PAGE_BILLING,
self::LANDING_PAGE_NO_PREFERENCE,
];
public const SHIPPING_PREFERENCE_GET_FROM_FILE = 'GET_FROM_FILE';
public const SHIPPING_PREFERENCE_NO_SHIPPING = 'NO_SHIPPING';
public const SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS = 'SET_PROVIDED_ADDRESS';
private const VALID_SHIPPING_PREFERENCE_VALUES = [
self::SHIPPING_PREFERENCE_GET_FROM_FILE,
self::SHIPPING_PREFERENCE_NO_SHIPPING,
self::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS,
];
public const USER_ACTION_CONTINUE = 'CONTINUE';
public const USER_ACTION_PAY_NOW = 'PAY_NOW';
private const VALID_USER_ACTION_VALUES = [
self::USER_ACTION_CONTINUE,
self::USER_ACTION_PAY_NOW,
];
private $brandName;
private $locale;
private $landingPage;
private $shippingPreference;
private $userAction;
private $returnUrl;
private $cancelUrl;
private $paymentMethod;
public function __construct(
string $returnUrl = '',
string $cancelUrl = '',
string $brandName = '',
string $locale = '',
string $landingPage = self::LANDING_PAGE_NO_PREFERENCE,
string $shippingPreference = self::SHIPPING_PREFERENCE_NO_SHIPPING,
string $userAction = self::USER_ACTION_CONTINUE
) {
if (! in_array($landingPage, self::VALID_LANDING_PAGE_VALUES, true)) {
throw new RuntimeException("Landingpage not correct");
}
if (! in_array($shippingPreference, self::VALID_SHIPPING_PREFERENCE_VALUES, true)) {
throw new RuntimeException("Shipping preference not correct");
}
if (! in_array($userAction, self::VALID_USER_ACTION_VALUES, true)) {
throw new RuntimeException("User action preference not correct");
}
$this->returnUrl = $returnUrl;
$this->cancelUrl = $cancelUrl;
$this->brandName = $brandName;
$this->locale = $locale;
$this->landingPage = $landingPage;
$this->shippingPreference = $shippingPreference;
$this->userAction = $userAction;
//Currently we have not implemented the payment method.
$this->paymentMethod = null;
}
public function brandName(): string
{
return $this->brandName;
}
public function locale(): string
{
return $this->locale;
}
public function landingPage(): string
{
return $this->landingPage;
}
public function shippingPreference(): string
{
return $this->shippingPreference;
}
public function userAction(): string
{
return $this->userAction;
}
public function returnUrl(): string
{
return $this->returnUrl;
}
public function cancelUrl(): string
{
return $this->cancelUrl;
}
/**
* Currently, we have not implemented this.
*
* If we would follow our schema, we would create a paymentMethod entity which could
* get returned here.
*/
public function paymentMethod(): ?\stdClass
{
return $this->paymentMethod;
}
public function toArray(): array
{
$data = [];
if ($this->userAction()) {
$data['user_action'] = $this->userAction();
}
if ($this->paymentMethod()) {
$data['payment_method'] = $this->paymentMethod();
}
if ($this->shippingPreference()) {
$data['shipping_preference'] = $this->shippingPreference();
}
if ($this->landingPage()) {
$data['landing_page'] = $this->landingPage();
}
if ($this->locale()) {
$data['locale'] = $this->locale();
}
if ($this->brandName()) {
$data['brand_name'] = $this->brandName();
}
if ($this->returnUrl()) {
$data['return_url'] = $this->returnUrl();
}
if ($this->cancelUrl()) {
$data['cancel_url'] = $this->cancelUrl();
}
if ($this->paymentMethod()) {
$data['payment_method'] = $this->paymentMethod();
}
return $data;
}
}

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Authorization
{
private $id;
private $authorizationStatus;
public function __construct(
string $id,
AuthorizationStatus $authorizationStatus
) {
$this->id = $id;
$this->authorizationStatus = $authorizationStatus;
}
public function id(): string
{
return $this->id;
}
public function status(): AuthorizationStatus
{
return $this->authorizationStatus;
}
public function toArray(): array
{
return [
'id' => $this->id,
'status' => $this->authorizationStatus->name(),
];
}
}

View file

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class AuthorizationStatus
{
public const INTERNAL = 'INTERNAL';
public const CREATED = 'CREATED';
public const CAPTURED = 'CAPTURED';
public const COMPLETED = 'COMPLETED';
public const DENIED = 'DENIED';
public const EXPIRED = 'EXPIRED';
public const PARTIALLY_CAPTURED = 'PARTIALLY_CAPTURED';
public const VOIDED = 'VOIDED';
public const PENDING = 'PENDING';
public const VALID_STATUS = [
self::INTERNAL,
self::CREATED,
self::CAPTURED,
self::COMPLETED,
self::DENIED,
self::EXPIRED,
self::PARTIALLY_CAPTURED,
self::VOIDED,
self::PENDING,
];
private $status;
public function __construct(string $status)
{
if (!in_array($status, self::VALID_STATUS, true)) {
throw new RuntimeException(
sprintf(
// translators: %s is the current status.
__("%s is not a valid status", 'woocmmerce-paypal-commerce-gateway'),
$status
)
);
}
$this->status = $status;
}
public static function asInternal(): AuthorizationStatus
{
return new self(self::INTERNAL);
}
public function is(string $status): bool
{
return $this->status === $status;
}
public function name(): string
{
return $this->status;
}
}

View file

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class CardAuthenticationResult
{
public const LIABILITY_SHIFT_POSSIBLE = 'POSSIBLE';
public const LIABILITY_SHIFT_NO = 'NO';
public const LIABILITY_SHIFT_UNKNOWN = 'UNKNOWN';
public const ENROLLMENT_STATUS_YES = 'Y';
public const ENROLLMENT_STATUS_NO = 'N';
public const ENROLLMENT_STATUS_UNAVAILABLE = 'U';
public const ENROLLMENT_STATUS_BYPASS = 'B';
public const AUTHENTICATION_RESULT_YES = 'Y';
public const AUTHENTICATION_RESULT_NO = 'N';
public const AUTHENTICATION_RESULT_REJECTED = 'R';
public const AUTHENTICATION_RESULT_ATTEMPTED = 'A';
public const AUTHENTICATION_RESULT_UNABLE = 'U';
public const AUTHENTICATION_RESULT_CHALLENGE_REQUIRED = 'C';
public const AUTHENTICATION_RESULT_INFO = 'I';
public const AUTHENTICATION_RESULT_DECOUPLED = 'D';
private $liabilityShift;
private $enrollmentStatus;
private $authenticationResult;
public function __construct(
string $liabilityShift,
string $enrollmentStatus,
string $authenticationResult
) {
$this->liabilityShift = strtoupper($liabilityShift);
$this->enrollmentStatus = strtoupper($enrollmentStatus);
$this->authenticationResult = strtoupper($authenticationResult);
}
public function liabilityShift(): string
{
return $this->liabilityShift;
}
public function enrollmentStatus(): string
{
return $this->enrollmentStatus;
}
public function authenticationResult(): string
{
return $this->authenticationResult;
}
public function toArray(): array
{
$data = [];
$data['liability_shift'] = $this->liabilityShift();
$data['three_d_secure'] = [
'enrollment_status' => $this->enrollmentStatus(),
'authentication_result' => $this->authenticationResult(),
];
return $data;
}
}

View file

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Item
{
public const PHYSICAL_GOODS = 'PHYSICAL_GOODS';
public const DIGITAL_GOODS = 'DIGITAL_GOODS';
private $name;
private $unitAmount;
private $quantity;
private $description;
private $tax;
private $sku;
private $category;
public function __construct(
string $name,
Money $unitAmount,
int $quantity,
string $description = '',
Money $tax = null,
string $sku = '',
string $category = 'PHYSICAL_GOODS'
) {
$this->name = $name;
$this->unitAmount = $unitAmount;
$this->quantity = $quantity;
$this->description = $description;
$this->tax = $tax;
$this->sku = $sku;
$this->category = ($category === self::DIGITAL_GOODS) ?
self::DIGITAL_GOODS : self::PHYSICAL_GOODS;
}
public function name(): string
{
return $this->name;
}
public function unitAmount(): Money
{
return $this->unitAmount;
}
public function quantity(): int
{
return $this->quantity;
}
public function description(): string
{
return $this->description;
}
public function tax(): ?Money
{
return $this->tax;
}
public function sku(): string
{
return $this->sku;
}
public function category(): string
{
return $this->category;
}
public function toArray(): array
{
$item = [
'name' => $this->name(),
'unit_amount' => $this->unitAmount()->toArray(),
'quantity' => $this->quantity(),
'description' => $this->description(),
'sku' => $this->sku(),
'category' => $this->category(),
];
if ($this->tax()) {
$item['tax'] = $this->tax()->toArray();
}
return $item;
}
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Money
{
private $currencyCode;
private $value;
public function __construct(float $value, string $currencyCode)
{
$this->value = $value;
$this->currencyCode = $currencyCode;
}
public function value(): float
{
return $this->value;
}
public function currencyCode(): string
{
return $this->currencyCode;
}
public function toArray(): array
{
return [
'currency_code' => $this->currencyCode(),
'value' => number_format($this->value(), 2),
];
}
}

View file

@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Order
{
private $id;
private $createTime;
private $purchaseUnits;
private $payer;
private $orderStatus;
private $intent;
private $updateTime;
private $applicationContext;
private $paymentSource;
/**
* Order constructor.
*
* @see https://developer.paypal.com/docs/api/orders/v2/#orders-create-response
*/
public function __construct(
string $id,
array $purchaseUnits,
OrderStatus $orderStatus,
ApplicationContext $applicationContext = null,
PaymentSource $paymentSource = null,
Payer $payer = null,
string $intent = 'CAPTURE',
\DateTime $createTime = null,
\DateTime $updateTime = null
) {
$this->id = $id;
$this->applicationContext = $applicationContext;
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
$this->purchaseUnits = array_values(array_filter(
$purchaseUnits,
static function ($unit): bool {
return is_a($unit, PurchaseUnit::class);
}
));
//phpcs:enable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
$this->payer = $payer;
$this->orderStatus = $orderStatus;
$this->intent = ($intent === 'CAPTURE') ? 'CAPTURE' : 'AUTHORIZE';
$this->purchaseUnits = $purchaseUnits;
$this->createTime = $createTime;
$this->updateTime = $updateTime;
$this->paymentSource = $paymentSource;
}
public function id(): string
{
return $this->id;
}
public function createTime(): ?\DateTime
{
return $this->createTime;
}
public function updateTime(): ?\DateTime
{
return $this->updateTime;
}
public function intent(): string
{
return $this->intent;
}
public function payer(): ?Payer
{
return $this->payer;
}
/**
* @return PurchaseUnit[]
*/
public function purchaseUnits(): array
{
return $this->purchaseUnits;
}
public function status(): OrderStatus
{
return $this->orderStatus;
}
public function applicationContext(): ?ApplicationContext
{
return $this->applicationContext;
}
public function paymentSource(): ?PaymentSource
{
return $this->paymentSource;
}
public function toArray(): array
{
$order = [
'id' => $this->id(),
'intent' => $this->intent(),
'status' => $this->status()->name(),
'purchase_units' => array_map(
static function (PurchaseUnit $unit): array {
return $unit->toArray();
},
$this->purchaseUnits()
),
];
if ($this->createTime()) {
$order['create_time'] = $this->createTime()->format(\DateTimeInterface::ISO8601);
}
if ($this->payer()) {
$order['payer'] = $this->payer()->toArray();
}
if ($this->updateTime()) {
$order['update_time'] = $this->updateTime()->format(\DateTimeInterface::ISO8601);
}
if ($this->applicationContext()) {
$order['application_context'] = $this->applicationContext()->toArray();
}
if ($this->paymentSource()) {
$order['payment_source'] = $this->paymentSource()->toArray();
}
return $order;
}
}

View file

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class OrderStatus
{
public const INTERNAL = 'INTERNAL';
public const CREATED = 'CREATED';
public const SAVED = 'SAVED';
public const APPROVED = 'APPROVED';
public const VOIDED = 'VOIDED';
public const COMPLETED = 'COMPLETED';
public const VALID_STATI = [
self::INTERNAL,
self::CREATED,
self::SAVED,
self::APPROVED,
self::VOIDED,
self::COMPLETED,
];
private $status;
public function __construct(string $status)
{
if (! in_array($status, self::VALID_STATI, true)) {
throw new RuntimeException(sprintf(
// translators: %s is the current status.
__("%s is not a valid status", "woocmmerce-paypal-commerce-gateway"),
$status
));
}
$this->status = $status;
}
public static function asInternal(): OrderStatus
{
return new self(self::INTERNAL);
}
public function is(string $status): bool
{
return $this->status === $status;
}
public function name(): string
{
return $this->status;
}
}

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
//phpcs:disable Inpsyde.CodeQuality.ElementNameMinimalLength.TooShort
class Patch
{
private $op;
private $path;
private $value;
public function __construct(string $op, string $path, array $value)
{
$this->op = $op;
$this->path = $path;
$this->value = $value;
}
public function op(): string
{
return $this->op;
}
public function path(): string
{
return $this->path;
}
// phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
public function value()
{
return $this->value;
}
// phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
public function toArray(): array
{
return [
'op' => $this->op(),
'value' => $this->value(),
'path' => $this->path(),
];
}
/**
* Needed for the move operation. We currently do not
* support the move operation.
*
* @return string
*/
public function from(): string
{
return '';
}
}

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PatchCollection
{
private $patches;
public function __construct(Patch ...$patches)
{
$this->patches = $patches;
}
/**
* @return Patch[]
*/
public function patches(): array
{
return $this->patches;
}
public function toArray(): array
{
return array_map(
static function (Patch $patch): array {
return $patch->toArray();
},
$this->patches()
);
}
}

View file

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
/**
* Class Payee
* The entity, which receives the money.
*
* @package Inpsyde\PayPalCommerce\ApiClient\Entity
*/
class Payee
{
private $email;
private $merchantId;
public function __construct(
string $email,
string $merchantId
) {
$this->email = $email;
$this->merchantId = $merchantId;
}
public function email(): string
{
return $this->email;
}
public function merchantId(): string
{
return $this->merchantId;
}
public function toArray(): array
{
$data = [
'email_address' => $this->email(),
];
if ($this->merchantId) {
$data['merchant_id'] = $this->merchantId();
}
return $data;
}
}

View file

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
/**
* Class Payer
* The customer who sends the money.
*
* @package Inpsyde\PayPalCommerce\ApiClient\Entity
*/
class Payer
{
private $name;
private $emailAddress;
private $payerId;
private $birthDate;
private $address;
private $phone;
private $taxInfo;
public function __construct(
PayerName $name,
string $emailAddress,
string $payerId,
Address $address,
\DateTime $birthDate = null,
PhoneWithType $phone = null,
PayerTaxInfo $taxInfo = null
) {
$this->name = $name;
$this->emailAddress = $emailAddress;
$this->payerId = $payerId;
$this->birthDate = $birthDate;
$this->address = $address;
$this->phone = $phone;
$this->taxInfo = $taxInfo;
}
public function name(): PayerName
{
return $this->name;
}
public function emailAddress(): string
{
return $this->emailAddress;
}
public function payerId(): string
{
return $this->payerId;
}
public function birthDate(): ?\DateTime
{
return $this->birthDate;
}
public function address(): Address
{
return $this->address;
}
public function phone(): ?PhoneWithType
{
return $this->phone;
}
public function taxInfo(): ?PayerTaxInfo
{
return $this->taxInfo;
}
public function toArray(): array
{
$payer = [
'name' => $this->name()->toArray(),
'email_address' => $this->emailAddress(),
'address' => $this->address()->toArray(),
];
if ($this->payerId()) {
$payer['payer_id'] = $this->payerId();
}
if ($this->phone()) {
$payer['phone'] = $this->phone()->toArray();
}
if ($this->taxInfo()) {
$payer['tax_info'] = $this->taxInfo()->toArray();
}
if ($this->birthDate()) {
$payer['birth_date'] = $this->birthDate()->format('Y-m-d');
}
return $payer;
}
}

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PayerName
{
private $givenName;
private $surname;
public function __construct(
string $givenName,
string $surname
) {
$this->givenName = $givenName;
$this->surname = $surname;
}
public function givenName(): string
{
return $this->givenName;
}
public function surname(): string
{
return $this->surname;
}
public function toArray(): array
{
return [
'given_name' => $this->givenName(),
'surname' => $this->surname(),
];
}
}

View file

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class PayerTaxInfo
{
public const VALID_TYPES = [
'BR_CPF',
'BR_CNPJ',
];
private $taxId;
private $type;
public function __construct(
string $taxId,
string $type
) {
if (! in_array($type, self::VALID_TYPES, true)) {
throw new RuntimeException(sprintf(
// translators: %s is the current type.
__("%s is not a valid tax type.", "woocommerce-paypal-commerce-gateway"),
$type
));
}
$this->taxId = $taxId;
$this->type = $type;
}
public function type(): string
{
return $this->type;
}
public function taxId(): string
{
return $this->taxId;
}
public function toArray(): array
{
return [
'tax_id' => $this->taxId(),
'tax_id_type' => $this->type(),
];
}
}

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PaymentMethod
{
public const PAYER_SELECTED_DEFAULT = 'PAYPAL';
public const PAYEE_PREFERRED_UNRESTRICTED = 'UNRESTRICTED';
public const PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED = 'IMMEDIATE_PAYMENT_REQUIRED';
private $preferred;
private $selected;
public function __construct(
string $preferred = self::PAYEE_PREFERRED_UNRESTRICTED,
string $selected = self::PAYER_SELECTED_DEFAULT
) {
$this->preferred = $preferred;
$this->selected = $selected;
}
public function payeePreferred(): string
{
return $this->preferred;
}
public function payerSelected(): string
{
return $this->selected;
}
public function toArray(): array
{
return [
'payee_preferred' => $this->payeePreferred(),
'payer_selected' => $this->payerSelected(),
];
}
}

View file

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PaymentSource
{
private $card;
private $wallet;
public function __construct(
PaymentSourceCard $card = null,
PaymentSourceWallet $wallet = null
) {
$this->card = $card;
$this->wallet = $wallet;
}
public function card(): ?PaymentSourceCard
{
return $this->card;
}
public function wallet(): ?PaymentSourceWallet
{
return $this->wallet;
}
public function toArray(): array
{
$data = [];
if ($this->card()) {
$data['card'] = $this->card()->toArray();
}
if ($this->wallet()) {
$data['wallet'] = $this->wallet()->toArray();
}
return $data;
}
}

View file

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PaymentSourceCard
{
private $lastDigits;
private $brand;
private $type;
private $authenticationResult;
public function __construct(
string $lastDigits,
string $brand,
string $type,
CardAuthenticationResult $authenticationResult = null
) {
$this->lastDigits = $lastDigits;
$this->brand = $brand;
$this->type = $type;
$this->authenticationResult = $authenticationResult;
}
public function lastDigits(): string
{
return $this->lastDigits;
}
public function brand(): string
{
return $this->brand;
}
public function type(): string
{
return $this->type;
}
public function authenticationResult(): ?CardAuthenticationResult
{
return $this->authenticationResult;
}
public function toArray(): array
{
$data = [
'last_digits' => $this->lastDigits(),
'brand' => $this->brand(),
'type' => $this->type(),
];
if ($this->authenticationResult()) {
$data['authentication_result'] = $this->authenticationResult()->toArray();
}
return $data;
}
}

View file

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PaymentSourceWallet
{
public function toArray(): array
{
return [];
}
}

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class PaymentToken
{
public const TYPE_PAYMENT_METHOD_TOKEN = 'PAYMENT_METHOD_TOKEN';
public const VALID_TYPES = [
self::TYPE_PAYMENT_METHOD_TOKEN,
];
private $id;
private $type;
public function __construct(string $id, string $type = self::TYPE_PAYMENT_METHOD_TOKEN)
{
if (! in_array($type, self::VALID_TYPES, true)) {
throw new RuntimeException(
__("Not a valid payment source type.", "woocommerce-paypal-commerce-gateway")
);
}
$this->id = $id;
$this->type = $type;
}
public function id(): string
{
return $this->id;
}
public function type(): string
{
return $this->type;
}
public function toArray(): array
{
return [
'id' => $this->id(),
'type' => $this->type(),
];
}
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Payments
{
private $authorizations;
public function __construct(Authorization ...$authorizations)
{
$this->authorizations = $authorizations;
}
public function toArray(): array
{
return [
'authorizations' => array_map(
static function (Authorization $authorization): array {
return $authorization->toArray();
},
$this->authorizations()
),
];
}
/**
* @return Authorization[]
**/
public function authorizations(): array
{
return $this->authorizations;
}
}

View file

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Phone
{
private $nationalNumber;
public function __construct(string $nationalNumber)
{
$this->nationalNumber = $nationalNumber;
}
public function nationalNumber(): string
{
return $this->nationalNumber;
}
public function toArray(): array
{
return [
'national_number' => $this->nationalNumber(),
];
}
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class PhoneWithType
{
public const VALLID_TYPES = [
'FAX',
'HOME',
'MOBILE',
'OTHER',
'PAGER',
];
private $type;
private $phone;
public function __construct(string $type, Phone $phone)
{
$this->type = in_array($type, self::VALLID_TYPES, true) ? $type : 'OTHER';
$this->phone = $phone;
}
public function type(): string
{
return $this->type;
}
public function phone(): Phone
{
return $this->phone;
}
public function toArray(): array
{
return [
'phone_type' => $this->type(),
'phone_number' => $this->phone()->toArray(),
];
}
}

View file

@ -0,0 +1,231 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
//phpcs:disable Inpsyde.CodeQuality.PropertyPerClassLimit.TooManyProperties
class PurchaseUnit
{
private $amount;
private $items;
private $shipping;
private $referenceId;
private $description;
private $payee;
private $customId;
private $invoiceId;
private $softDescriptor;
private $payments;
/**
* @var bool
*/
private $containsPhysicalGoodsItems = false;
public function __construct(
Amount $amount,
array $items = [],
Shipping $shipping = null,
string $referenceId = 'default',
string $description = '',
Payee $payee = null,
string $customId = '',
string $invoiceId = '',
string $softDescriptor = '',
Payments $payments = null
) {
$this->amount = $amount;
$this->shipping = $shipping;
$this->referenceId = $referenceId;
$this->description = $description;
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
$this->items = array_values(array_filter(
$items,
function ($item): bool {
$isItem = is_a($item, Item::class);
/**
* @var Item $item
*/
if ($isItem && Item::PHYSICAL_GOODS === $item->category()) {
$this->containsPhysicalGoodsItems = true;
}
return $isItem;
}
));
//phpcs:enable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
$this->payee = $payee;
$this->customId = $customId;
$this->invoiceId = $invoiceId;
$this->softDescriptor = $softDescriptor;
$this->payments = $payments;
}
public function amount(): Amount
{
return $this->amount;
}
public function shipping(): ?Shipping
{
return $this->shipping;
}
public function referenceId(): string
{
return $this->referenceId;
}
public function description(): string
{
return $this->description;
}
public function customId(): string
{
return $this->customId;
}
public function invoiceId(): string
{
return $this->invoiceId;
}
public function softDescriptor(): string
{
return $this->softDescriptor;
}
public function payee(): ?Payee
{
return $this->payee;
}
public function payments(): ?Payments
{
return $this->payments;
}
/**
* @return Item[]
*/
public function items(): array
{
return $this->items;
}
public function containsPhysicalGoodsItems(): bool
{
return $this->containsPhysicalGoodsItems;
}
public function toArray(): array
{
$purchaseUnit = [
'reference_id' => $this->referenceId(),
'amount' => $this->amount()->toArray(),
'description' => $this->description(),
'items' => array_map(
static function (Item $item): array {
return $item->toArray();
},
$this->items()
),
];
if ($this->ditchItemsWhenMismatch($this->amount(), ...$this->items())) {
unset($purchaseUnit['items']);
unset($purchaseUnit['amount']['breakdown']);
}
if ($this->payee()) {
$purchaseUnit['payee'] = $this->payee()->toArray();
}
if ($this->payments()) {
$purchaseUnit['payments'] = $this->payments()->toArray();
}
if ($this->shipping()) {
$purchaseUnit['shipping'] = $this->shipping()->toArray();
}
if ($this->customId()) {
$purchaseUnit['custom_id'] = $this->customId();
}
if ($this->invoiceId()) {
$purchaseUnit['invoice_id'] = $this->invoiceId();
}
if ($this->softDescriptor()) {
$purchaseUnit['soft_descriptor'] = $this->softDescriptor();
}
return $purchaseUnit;
}
/**
* All money values send to PayPal can only have 2 decimal points. Woocommerce internally does
* not have this restriction. Therefore the totals of the cart in Woocommerce and the totals
* of the rounded money values of the items, we send to PayPal, can differ. In those cases,
* we can not send the line items.
*
* @param Amount $amount
* @param Item ...$items
* @return bool
*/
//phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
private function ditchItemsWhenMismatch(Amount $amount, Item ...$items): bool
{
$feeItemsTotal = ($amount->breakdown() && $amount->breakdown()->itemTotal()) ?
$amount->breakdown()->itemTotal()->value() : null;
$feeTaxTotal = ($amount->breakdown() && $amount->breakdown()->taxTotal()) ?
$amount->breakdown()->taxTotal()->value() : null;
foreach ($items as $item) {
if (null !== $feeItemsTotal) {
$feeItemsTotal -= $item->unitAmount()->value() * $item->quantity();
}
if (null !== $feeTaxTotal) {
$feeTaxTotal -= $item->tax()->value() * $item->quantity();
}
}
$feeItemsTotal = round($feeItemsTotal, 2);
$feeTaxTotal = round($feeTaxTotal, 2);
if ($feeItemsTotal !== 0.0 || $feeTaxTotal !== 0.0) {
return true;
}
$breakdown = $this->amount()->breakdown();
if (! $breakdown) {
return false;
}
$amountTotal = 0;
if ($breakdown->shipping()) {
$amountTotal += $breakdown->shipping()->value();
}
if ($breakdown->itemTotal()) {
$amountTotal += $breakdown->itemTotal()->value();
}
if ($breakdown->discount()) {
$amountTotal -= $breakdown->discount()->value();
}
if ($breakdown->taxTotal()) {
$amountTotal += $breakdown->taxTotal()->value();
}
if ($breakdown->shippingDiscount()) {
$amountTotal -= $breakdown->shippingDiscount()->value();
}
if ($breakdown->handling()) {
$amountTotal += $breakdown->handling()->value();
}
if ($breakdown->insurance()) {
$amountTotal += $breakdown->insurance()->value();
}
$amountValue = $this->amount()->value();
$needsToDitch = (string) $amountTotal !== (string) $amountValue;
return $needsToDitch;
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Shipping
{
private $name;
private $address;
public function __construct(string $name, Address $address)
{
$this->name = $name;
$this->address = $address;
}
public function name(): string
{
return $this->name;
}
public function address(): Address
{
return $this->address;
}
public function toArray(): array
{
return [
'name' => [
'full_name' => $this->name(),
],
'address' => $this->address()->toArray(),
];
}
}

View file

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class Token
{
private $json;
private $created;
public function __construct(\stdClass $json)
{
if (! isset($json->created)) {
$json->created = time();
}
if (! $this->validate($json)) {
throw new RuntimeException("Token not valid");
}
$this->json = $json;
}
public function expirationTimestamp(): int
{
return $this->json->created + $this->json->expires_in;
}
public function token(): string
{
return (string) $this->json->token;
}
public function isValid(): bool
{
return time() < $this->json->created + $this->json->expires_in;
}
public function asJson(): string
{
return json_encode($this->json);
}
public static function fromJson(string $json): self
{
$json = (object) json_decode($json);
if (isset($json->access_token) || isset($json->client_token)) {
$json->token = isset($json->access_token) ? $json->access_token : $json->client_token;
}
return new Token($json);
}
private function validate(\stdClass $json): bool
{
$propertyMap = [
'created' => 'is_int',
'expires_in' => 'is_int',
'token' => 'is_string',
];
foreach ($propertyMap as $property => $validator) {
if (! isset($json->{$property}) || ! $validator($json->{$property})) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
class Webhook
{
private $id;
private $url;
private $eventTypes;
public function __construct(string $url, array $eventTypes, string $id = '')
{
$this->url = $url;
$this->eventTypes = $eventTypes;
$this->id = $id;
}
public function id(): string
{
return $this->id;
}
public function url(): string
{
return $this->url;
}
public function eventTypes(): array
{
return $this->eventTypes;
}
public function toArray(): array
{
$data = [
'url' => $this->url(),
'event_types' => $this->eventTypes(),
];
if ($this->id()) {
$data['id'] = $this->id();
}
return $data;
}
}

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Exception;
use Psr\Container\NotFoundExceptionInterface;
use Exception;
class NotFoundException extends Exception implements NotFoundExceptionInterface
{
}

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Exception;
class PayPalApiException extends RuntimeException
{
private $response;
private $statusCode;
public function __construct(\stdClass $response = null, int $statusCode = 0)
{
if (is_null($response)) {
$response = new \stdClass();
}
if (! isset($response->message)) {
$response->message = __(
'Unknown error while connecting to PayPal.',
'woocommerce-paypal-commerce-gateway'
);
}
if (! isset($response->name)) {
$response->name = __('Error', 'woocommerce-paypal-commerce-gateway');
}
if (! isset($response->details)) {
$response->details = [];
}
if (! isset($response->links) || ! is_array($response->links)) {
$response->links = [];
}
$this->response = $response;
$this->statusCode = $statusCode;
$message = $response->message;
if ($response->name) {
$message = '[' . $response->name . '] ' . $message;
}
foreach ($response->links as $link) {
if (isset($link->rel) && $link->rel === 'information_link') {
$message .= ' ' . $link->href;
}
}
parent::__construct($message, $statusCode);
}
public function name(): string
{
return $this->response->name;
}
public function details(): array
{
return $this->response->details;
}
public function hasDetail(string $issue): bool
{
foreach ($this->details() as $detail) {
if (isset($detail->issue) && $detail->issue === $issue) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Exception;
class RuntimeException extends \RuntimeException
{
}

View file

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Address;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class AddressFactory
{
public function __construct()
{
}
public function fromWcCustomer(\WC_Customer $customer, string $type = 'shipping'): Address
{
return new Address(
($type === 'shipping') ?
$customer->get_shipping_country() : $customer->get_billing_country(),
($type === 'shipping') ?
$customer->get_shipping_address_1() : $customer->get_billing_address_1(),
($type === 'shipping') ?
$customer->get_shipping_address_2() : $customer->get_billing_address_2(),
($type === 'shipping') ?
$customer->get_shipping_state() : $customer->get_billing_state(),
($type === 'shipping') ?
$customer->get_shipping_city() : $customer->get_billing_city(),
($type === 'shipping') ?
$customer->get_shipping_postcode() : $customer->get_billing_postcode(),
);
}
public function fromWcOrder(\WC_Order $order): Address
{
return new Address(
$order->get_shipping_country(),
$order->get_shipping_address_1(),
$order->get_shipping_address_2(),
$order->get_shipping_state(),
$order->get_shipping_city(),
$order->get_shipping_postcode()
);
}
public function fromPayPalRequest(\stdClass $data): Address
{
if (! isset($data->country_code)) {
throw new RuntimeException(
__('No country given for address.', 'woocommerce-paypal-commerce-gateway')
);
}
return new Address(
$data->country_code,
(isset($data->address_line_1)) ? $data->address_line_1 : '',
(isset($data->address_line_2)) ? $data->address_line_2 : '',
(isset($data->admin_area_1)) ? $data->admin_area_1 : '',
(isset($data->admin_area_2)) ? $data->admin_area_2 : '',
(isset($data->postal_code)) ? $data->postal_code : ''
);
}
}

View file

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Amount;
use Inpsyde\PayPalCommerce\ApiClient\Entity\AmountBreakdown;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Money;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class AmountFactory
{
private $itemFactory;
public function __construct(ItemFactory $itemFactory)
{
$this->itemFactory = $itemFactory;
}
public function fromWcCart(\WC_Cart $cart): Amount
{
$currency = get_woocommerce_currency();
$total = new Money((float) $cart->get_total('numeric'), $currency);
$itemsTotal = $cart->get_cart_contents_total() + $cart->get_discount_total();
$itemsTotal = new Money((float) $itemsTotal, $currency);
$shipping = new Money(
(float) $cart->get_shipping_total() + $cart->get_shipping_tax(),
$currency
);
$taxes = new Money(
(float) $cart->get_cart_contents_tax() + (float) $cart->get_discount_tax(),
$currency
);
$discount = null;
if ($cart->get_discount_total()) {
$discount = new Money(
(float) $cart->get_discount_total() + $cart->get_discount_tax(),
$currency
);
}
//ToDo: Evaluate if more is needed? Fees?
$breakdown = new AmountBreakdown(
$itemsTotal,
$shipping,
$taxes,
null, // insurance?
null, // handling?
null, //shipping discounts?
$discount
);
$amount = new Amount(
$total,
$breakdown
);
return $amount;
}
public function fromWcOrder(\WC_Order $order): Amount
{
$currency = $order->get_currency();
$items = $this->itemFactory->fromWcOrder($order);
$total = new Money((float) $order->get_total(), $currency);
$itemsTotal = new Money((float)array_reduce(
$items,
static function (float $total, Item $item): float {
return $total + $item->quantity() * $item->unitAmount()->value();
},
0
), $currency);
$shipping = new Money(
(float) $order->get_shipping_total() + (float) $order->get_shipping_tax(),
$currency
);
$taxes = new Money((float)array_reduce(
$items,
static function (float $total, Item $item): float {
return $total + $item->quantity() * $item->tax()->value();
},
0
), $currency);
$discount = null;
if ((float) $order->get_total_discount(false)) {
$discount = new Money(
(float) $order->get_total_discount(false),
$currency
);
}
//ToDo: Evaluate if more is needed? Fees?
$breakdown = new AmountBreakdown(
$itemsTotal,
$shipping,
$taxes,
null, // insurance?
null, // handling?
null, //shipping discounts?
$discount
);
$amount = new Amount(
$total,
$breakdown
);
return $amount;
}
public function fromPayPalResponse(\stdClass $data): Amount
{
if (! isset($data->value) || ! is_numeric($data->value)) {
throw new RuntimeException(__("No value given", "woocommerce-paypal-commerce-gateway"));
}
if (! isset($data->currency_code)) {
throw new RuntimeException(
__("No currency given", "woocommerce-paypal-commerce-gateway")
);
}
$money = new Money((float) $data->value, $data->currency_code);
$breakdown = (isset($data->breakdown)) ? $this->breakdown($data->breakdown) : null;
return new Amount($money, $breakdown);
}
private function breakDown(\stdClass $data): AmountBreakdown
{
/**
* The order of the keys equals the necessary order of the constructor arguments.
*/
$orderedConstructorKeys = [
'item_total',
'shipping',
'tax_total',
'handling',
'insurance',
'shipping_discount',
'discount',
];
$money = [];
foreach ($orderedConstructorKeys as $key) {
if (! isset($data->{$key})) {
$money[] = null;
continue;
}
$item = $data->{$key};
if (! isset($item->value) || ! is_numeric($item->value)) {
throw new RuntimeException(sprintf(
// translators: %s is the current breakdown key.
__("No value given for breakdown %s", "woocommerce-paypal-commerce-gateway"),
$key
));
}
if (! isset($item->currency_code)) {
throw new RuntimeException(sprintf(
// translators: %s is the current breakdown key.
__("No currency given for breakdown %s", "woocommerce-paypal-commerce-gateway"),
$key
));
}
$money[] = new Money((float) $item->value, $item->currency_code);
}
return new AmountBreakdown(...$money);
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ApplicationContext;
class ApplicationContextFactory
{
public function fromPayPalResponse(\stdClass $data): ApplicationContext
{
return new ApplicationContext(
isset($data->return_url) ?
$data->return_url : '',
isset($data->cancel_url) ?
$data->cancel_url : '',
isset($data->brand_name) ?
$data->brand_name : '',
isset($data->locale) ?
$data->locale : '',
isset($data->landing_page) ?
$data->landing_page : ApplicationContext::LANDING_PAGE_NO_PREFERENCE,
isset($data->shipping_preference) ?
$data->shipping_preference : ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE,
isset($data->user_action) ?
$data->user_action : ApplicationContext::USER_ACTION_CONTINUE,
);
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class AuthorizationFactory
{
public function fromPayPalRequest(\stdClass $data): Authorization
{
if (!isset($data->id)) {
throw new RuntimeException(
__('Does not contain an id.', 'woocommerce-paypal-commerce-gateway')
);
}
if (!isset($data->status)) {
throw new RuntimeException(
__('Does not contain status.', 'woocommerce-paypal-commerce-gateway')
);
}
return new Authorization(
$data->id,
new AuthorizationStatus($data->status)
);
}
}

View file

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Money;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class ItemFactory
{
public function __construct()
{
}
public function fromWcCart(\WC_Cart $cart): array
{
$currency = get_woocommerce_currency();
$items = array_map(
static function (array $item) use ($currency): Item {
$product = $item['data'];
/**
* @var \WC_Product $product
*/
$quantity = (int) $item['quantity'];
$price = (float) wc_get_price_including_tax($product);
$priceWithoutTax = (float) wc_get_price_excluding_tax($product);
$priceWithoutTaxRounded = round($priceWithoutTax, 2);
$tax = round($price - $priceWithoutTaxRounded, 2);
$tax = new Money($tax, $currency);
return new Item(
mb_substr($product->get_name(), 0, 127),
new Money($priceWithoutTaxRounded, $currency),
$quantity,
mb_substr($product->get_description(), 0, 127),
$tax,
$product->get_sku(),
($product->is_virtual()) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
);
},
$cart->get_cart_contents()
);
return $items;
}
/**
* @param \WC_Order $order
* @return Item[]
*/
public function fromWcOrder(\WC_Order $order): array
{
return array_map(
function (\WC_Order_Item_Product $item) use ($order): Item {
return $this->fromWcOrderLineItem($item, $order);
},
$order->get_items('line_item')
);
}
private function fromWcOrderLineItem(\WC_Order_Item_Product $item, \WC_Order $order): Item
{
$currency = $order->get_currency();
$product = $item->get_product();
/**
* @var \WC_Product $product
*/
$quantity = $item->get_quantity();
$price = (float) $order->get_item_subtotal($item, true);
$priceWithoutTax = (float) $order->get_item_subtotal($item, false);
$priceWithoutTaxRounded = round($priceWithoutTax, 2);
$tax = round($price - $priceWithoutTaxRounded, 2);
$tax = new Money($tax, $currency);
return new Item(
mb_substr($product->get_name(), 0, 127),
new Money($priceWithoutTaxRounded, $currency),
$quantity,
mb_substr($product->get_description(), 0, 127),
$tax,
$product->get_sku(),
($product->is_virtual()) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
);
}
public function fromPayPalResponse(\stdClass $data): Item
{
if (! isset($data->name)) {
throw new RuntimeException(
__("No name for item given", "woocommerce-paypal-commerce-gateway")
);
}
if (! isset($data->quantity) || ! is_numeric($data->quantity)) {
throw new RuntimeException(
__("No quantity for item given", "woocommerce-paypal-commerce-gateway")
);
}
if (! isset($data->unit_amount->value) || ! isset($data->unit_amount->currency_code)) {
throw new RuntimeException(
__("No money values for item given", "woocommerce-paypal-commerce-gateway")
);
}
$unitAmount = new Money((float) $data->unit_amount->value, $data->unit_amount->currency_code);
$description = (isset($data->description)) ? $data->description : '';
$tax = (isset($data->tax)) ?
new Money((float) $data->tax->value, $data->tax->currency_code)
: null;
$sku = (isset($data->sku)) ? $data->sku : '';
$category = (isset($data->category)) ? $data->category : 'PHYSICAL_GOODS';
return new Item(
$data->name,
$unitAmount,
(int) $data->quantity,
$description,
$tax,
$sku,
$category
);
}
}

View file

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
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\Repository\ApplicationContextRepository;
class OrderFactory
{
private $purchaseUnitFactory;
private $payerFactory;
private $applicationContextRepository;
private $applicationContextFactory;
private $paymentSourceFactory;
public function __construct(
PurchaseUnitFactory $purchaseUnitFactory,
PayerFactory $payerFactory,
ApplicationContextRepository $applicationContextRepository,
ApplicationContextFactory $applicationContextFactory,
PaymentSourceFactory $paymentSourceFactory
) {
$this->purchaseUnitFactory = $purchaseUnitFactory;
$this->payerFactory = $payerFactory;
$this->applicationContextRepository = $applicationContextRepository;
$this->applicationContextFactory = $applicationContextFactory;
$this->paymentSourceFactory = $paymentSourceFactory;
}
public function fromWcOrder(\WC_Order $wcOrder, Order $order): Order
{
$purchaseUnits = [$this->purchaseUnitFactory->fromWcOrder($wcOrder)];
return new Order(
$order->id(),
$purchaseUnits,
$order->status(),
$order->applicationContext(),
$order->paymentSource(),
$order->payer(),
$order->intent(),
$order->createTime(),
$order->updateTime()
);
}
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
public function fromPayPalResponse(\stdClass $orderData): Order
{
if (! isset($orderData->id)) {
throw new RuntimeException(
__('Order does not contain an id.', 'woocommerce-paypal-commerce-gateway')
);
}
if (! isset($orderData->purchase_units) || !is_array($orderData->purchase_units)) {
throw new RuntimeException(
__('Order does not contain items.', 'woocommerce-paypal-commerce-gateway')
);
}
if (! isset($orderData->status)) {
throw new RuntimeException(
__('Order does not contain status.', 'woocommerce-paypal-commerce-gateway')
);
}
if (! isset($orderData->intent)) {
throw new RuntimeException(
__('Order does not contain intent.', 'woocommerce-paypal-commerce-gateway')
);
}
$purchaseUnits = array_map(
function (\stdClass $data): PurchaseUnit {
return $this->purchaseUnitFactory->fromPayPalResponse($data);
},
$orderData->purchase_units
);
$createTime = (isset($orderData->create_time)) ?
\DateTime::createFromFormat(\DateTime::ISO8601, $orderData->create_time)
: null;
$updateTime = (isset($orderData->update_time)) ?
\DateTime::createFromFormat(\DateTime::ISO8601, $orderData->update_time)
: null;
$payer = (isset($orderData->payer)) ?
$this->payerFactory->fromPayPalResponse($orderData->payer)
: null;
$applicationContext = (isset($orderData->application_context)) ?
$this->applicationContextFactory->fromPayPalResponse($orderData->application_context)
: null;
$paymentSource = (isset($orderData->payment_source)) ?
$this->paymentSourceFactory->fromPayPalResponse($orderData->payment_source) :
null;
return new Order(
$orderData->id,
$purchaseUnits,
new OrderStatus($orderData->status),
$applicationContext,
$paymentSource,
$payer,
$orderData->intent,
$createTime,
$updateTime
);
}
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
}

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Patch;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PatchCollection;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
class PatchCollectionFactory
{
public function fromOrders(Order $from, Order $to): PatchCollection
{
$allPatches = [];
$allPatches += $this->purchaseUnits($from->purchaseUnits(), $to->purchaseUnits());
return new PatchCollection(...$allPatches);
}
/**
* @param PurchaseUnit[] $from
* @param PurchaseUnit[] $to
* @return Patch[]
*/
private function purchaseUnits(array $from, array $to): array
{
$patches = [];
$path = '/purchase_units';
foreach ($to as $purchaseUnitTo) {
$needsUpdate = ! count(
array_filter(
$from,
static function (PurchaseUnit $unit) use ($purchaseUnitTo): bool {
//phpcs:disable WordPress.PHP.StrictComparisons.LooseComparison
// Loose comparison needed to compare two objects.
return $unit == $purchaseUnitTo;
//phpcs:enable WordPress.PHP.StrictComparisons.LooseComparison
}
)
);
$needsUpdate = true;
if (!$needsUpdate) {
continue;
}
$purchaseUnitFrom = current(array_filter(
$from,
static function (PurchaseUnit $unit) use ($purchaseUnitTo): bool {
return $purchaseUnitTo->referenceId() === $unit->referenceId();
}
));
$operation = $purchaseUnitFrom ? 'replace' : 'add';
$value = $purchaseUnitTo->toArray();
$patches[] = new Patch(
$operation,
$path . "/@reference_id=='" . $purchaseUnitTo->referenceId() . "'",
$value
);
}
return $patches;
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payee;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class PayeeFactory
{
public function fromPayPalResponse(\stdClass $data): ?Payee
{
if (! isset($data->email_address)) {
throw new RuntimeException(
__("No email for payee given.", "woocommerce-paypal-commerce-gateway")
);
}
$merchantId = (isset($data->merchant_id)) ? $data->merchant_id : '';
return new Payee($data->email_address, $merchantId);
}
}

View file

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PayerName;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PayerTaxInfo;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Phone;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PhoneWithType;
class PayerFactory
{
private $addressFactory;
public function __construct(AddressFactory $addressFactory)
{
$this->addressFactory = $addressFactory;
}
public function fromCustomer(\WC_Customer $customer): Payer
{
$payerId = '';
$birthdate = null;
$phone = null;
if ($customer->get_billing_phone()) {
// make sure the phone number contains only numbers and is max 14. chars long.
$nationalNumber = $customer->get_billing_phone();
$nationalNumber = preg_replace("/[^0-9]/", "", $nationalNumber);
$nationalNumber = substr($nationalNumber, 0, 14);
$phone = new PhoneWithType(
'HOME',
new Phone($nationalNumber)
);
}
return new Payer(
new PayerName(
$customer->get_billing_first_name(),
$customer->get_billing_last_name()
),
$customer->get_billing_email(),
$payerId,
$this->addressFactory->fromWcCustomer($customer, 'billing'),
$birthdate,
$phone
);
}
public function fromPayPalResponse(\stdClass $data): Payer
{
$address = $this->addressFactory->fromPayPalRequest($data->address);
$payerName = new PayerName(
isset($data->name->given_name) ? (string) $data->name->given_name : '',
isset($data->name->surname) ? (string) $data->name->surname : ''
);
// TODO deal with phones without type instead of passing a invalid type
$phone = (isset($data->phone)) ? new PhoneWithType(
(isset($data->phone->phone_type)) ? $data->phone->phone_type : 'undefined',
new Phone(
$data->phone->phone_number->national_number
)
) : null;
$taxInfo = (isset($data->tax_info)) ?
new PayerTaxInfo($data->tax_info->tax_id, $data->tax_info->tax_id_type)
: null;
$birthDate = (isset($data->birth_date)) ?
\DateTime::createFromFormat('Y-m-d', $data->birth_date)
: null;
return new Payer(
$payerName,
isset($data->email_address) ? $data->email_address : '',
(isset($data->payer_id)) ? $data->payer_id : '',
$address,
$birthDate,
$phone,
$taxInfo
);
}
}

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentSource;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentSourceCard;
class PaymentSourceFactory
{
public function fromPayPalResponse(\stdClass $data): PaymentSource
{
$card = null;
$wallet = null;
if (isset($data->card)) {
$authenticationResult = null;
if (isset($data->card->authentication_result)) {
$authenticationResult = new CardAuthenticationResult(
isset($data->card->authentication_result->liability_shift) ?
(string) $data->card->authentication_result->liability_shift : '',
isset($data->card->authentication_result->three_d_secure->enrollment_status) ?
(string) $data->card->authentication_result->three_d_secure->enrollment_status : '',
isset($data->card->authentication_result->three_d_secure->authentication_result) ?
(string) $data->card->authentication_result->three_d_secure->authentication_result : ''
);
}
$card = new PaymentSourceCard(
isset($data->card->last_digits) ? (string) $data->card->last_digits : '',
isset($data->card->brand) ? (string) $data->card->brand : '',
isset($data->card->type) ? (string) $data->card->type : '',
$authenticationResult
);
}
return new PaymentSource($card, $wallet);
}
}

View file

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentToken;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class PaymentTokenFactory
{
public function fromPayPalResponse(\stdClass $data): PaymentToken
{
if (! isset($data->id)) {
throw new RuntimeException(
__("No id for payment token given", "woocommerce-paypal-commerce-gateway")
);
}
return new PaymentToken(
$data->id,
(isset($data->type)) ? $data->type : PaymentToken::TYPE_PAYMENT_METHOD_TOKEN
);
}
public function fromArray(array $data): PaymentToken
{
return $this->fromPayPalResponse((object) $data);
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payments;
class PaymentsFactory
{
private $authorizationsFactory;
public function __construct(
AuthorizationFactory $authorizationsFactory
) {
$this->authorizationsFactory = $authorizationsFactory;
}
public function fromPayPalResponse(\stdClass $data): Payments
{
$authorizations = array_map(
function (\stdClass $authorization): Authorization {
return $this->authorizationsFactory->fromPayPalRequest($authorization);
},
isset($data->authorizations) ? $data->authorizations : []
);
$payments = new Payments(...$authorizations);
return $payments;
}
}

View file

@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository;
class PurchaseUnitFactory
{
private $amountFactory;
private $payeeRepository;
private $payeeFactory;
private $itemFactory;
private $shippingFactory;
private $paymentsFactory;
private $prefix;
public function __construct(
AmountFactory $amountFactory,
PayeeRepository $payeeRepository,
PayeeFactory $payeeFactory,
ItemFactory $itemFactory,
ShippingFactory $shippingFactory,
PaymentsFactory $paymentsFactory,
string $prefix = 'WC-'
) {
$this->amountFactory = $amountFactory;
$this->payeeRepository = $payeeRepository;
$this->payeeFactory = $payeeFactory;
$this->itemFactory = $itemFactory;
$this->shippingFactory = $shippingFactory;
$this->paymentsFactory = $paymentsFactory;
$this->prefix = $prefix;
}
public function fromWcOrder(\WC_Order $order): PurchaseUnit
{
$amount = $this->amountFactory->fromWcOrder($order);
$items = $this->itemFactory->fromWcOrder($order);
$shipping = $this->shippingFactory->fromWcOrder($order);
if (
empty($shipping->address()->countryCode()) ||
($shipping->address()->countryCode() && !$shipping->address()->postalCode())
) {
$shipping = null;
}
$referenceId = 'default';
$description = '';
$payee = $this->payeeRepository->payee();
$wcOrderId = $order->get_id();
$customId = $this->prefix . $wcOrderId;
$invoiceId = $this->prefix . $wcOrderId;
$softDescriptor = '';
$purchaseUnit = new PurchaseUnit(
$amount,
$items,
$shipping,
$referenceId,
$description,
$payee,
$customId,
$invoiceId,
$softDescriptor
);
return $purchaseUnit;
}
public function fromWcCart(\WC_Cart $cart): PurchaseUnit
{
$amount = $this->amountFactory->fromWcCart($cart);
$items = $this->itemFactory->fromWcCart($cart);
$shipping = null;
$customer = \WC()->customer;
if (is_a($customer, \WC_Customer::class)) {
$shipping = $this->shippingFactory->fromWcCustomer(\WC()->customer);
if (
! $shipping->address()->countryCode()
|| ($shipping->address()->countryCode() && !$shipping->address()->postalCode())
) {
$shipping = null;
}
}
$referenceId = 'default';
$description = '';
$payee = $this->payeeRepository->payee();
$customId = '';
$invoiceId = '';
$softDescriptor = '';
$purchaseUnit = new PurchaseUnit(
$amount,
$items,
$shipping,
$referenceId,
$description,
$payee,
$customId,
$invoiceId,
$softDescriptor
);
return $purchaseUnit;
}
public function fromPayPalResponse(\stdClass $data): PurchaseUnit
{
if (! isset($data->reference_id) || ! is_string($data->reference_id)) {
throw new RuntimeException(
__("No reference ID given.", "woocommercepaypal-commerce-gateway")
);
}
$amount = $this->amountFactory->fromPayPalResponse($data->amount);
$description = (isset($data->description)) ? $data->description : '';
$customId = (isset($data->custom_id)) ? $data->custom_id : '';
$invoiceId = (isset($data->invoice_id)) ? $data->invoice_id : '';
$softDescriptor = (isset($data->soft_descriptor)) ? $data->soft_descriptor : '';
$items = [];
if (isset($data->items) && is_array($data->items)) {
$items = array_map(
function (\stdClass $item): Item {
return $this->itemFactory->fromPayPalResponse($item);
},
$data->items
);
}
$payee = isset($data->payee) ? $this->payeeFactory->fromPayPalResponse($data->payee) : null;
$shipping = null;
try {
if (isset($data->shipping)) {
$shipping = $this->shippingFactory->fromPayPalResponse($data->shipping);
}
} catch (RuntimeException $error) {
}
$payments = null;
try {
if (isset($data->payments)) {
$payments = $this->paymentsFactory->fromPayPalResponse($data->payments);
}
} catch (RuntimeException $error) {
}
$purchaseUnit = new PurchaseUnit(
$amount,
$items,
$shipping,
$data->reference_id,
$description,
$payee,
$customId,
$invoiceId,
$softDescriptor,
$payments
);
return $purchaseUnit;
}
}

View file

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Shipping;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class ShippingFactory
{
private $addressFactory;
public function __construct(AddressFactory $addressFactory)
{
$this->addressFactory = $addressFactory;
}
public function fromWcCustomer(\WC_Customer $customer): Shipping
{
// Replicates the Behavior of \WC_Order::get_formatted_shipping_full_name()
$fullName = sprintf(
// translators: %1$s is the first name and %2$s is the second name. wc translation.
_x('%1$s %2$s', 'full name', 'woocommerce'),
$customer->get_shipping_first_name(),
$customer->get_shipping_last_name()
);
$address = $this->addressFactory->fromWcCustomer($customer);
return new Shipping(
$fullName,
$address
);
}
public function fromWcOrder(\WC_Order $order): Shipping
{
$fullName = $order->get_formatted_shipping_full_name();
$address = $this->addressFactory->fromWcOrder($order);
return new Shipping(
$fullName,
$address
);
}
public function fromPayPalResponse(\stdClass $data): Shipping
{
if (! isset($data->name->full_name)) {
throw new RuntimeException(
__("No name was given for shipping.", "woocommerce-paypal-commerce-gateway")
);
}
if (! isset($data->address)) {
throw new RuntimeException(
__("No address was given for shipping.", "woocommerce-paypal-commerce-gateway")
);
}
$address = $this->addressFactory->fromPayPalRequest($data->address);
return new Shipping(
$data->name->full_name,
$address
);
}
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Webhook;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
class WebhookFactory
{
public function forUrlAndEvents(string $url, array $eventTypes): Webhook
{
$eventTypes = array_map(
static function (string $type): array {
return ["name" => $type];
},
$eventTypes
);
return new Webhook(
$url,
$eventTypes
);
}
public function fromArray(array $data): Webhook
{
return $this->fromPayPalResponse((object) $data);
}
public function fromPayPalResponse(\stdClass $data): Webhook
{
if (! isset($data->id)) {
throw new RuntimeException(
__("No id for webhook given.", "woocommerce-paypal-commerce-gateway")
);
}
if (! isset($data->url)) {
throw new RuntimeException(
__("No URL for webhook given.", "woocommerce-paypal-commerce-gateway")
);
}
if (! isset($data->event_types)) {
throw new RuntimeException(
__("No event types for webhook given.", "woocommerce-paypal-commerce-gateway")
);
}
return new Webhook(
(string) $data->url,
(array) $data->event_types,
(string) $data->id
);
}
}

View file

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Helper;
class DccApplies
{
private $allowedCountryCurrencyMatrix = [
'AU' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'BE' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'BG' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'CY' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'CZ' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'DK' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'EE' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'FI' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'FR' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'GR' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'HU' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'IT' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'LV' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'LI' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'LT' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'LU' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'MT' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'NL' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'NO' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'PL' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'PT' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'RO' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'SK' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'SI' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'ES' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'SE' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
'US' => [
'AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'USD',
],
'GB' => [
'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD',
'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD',
],
];
public function forCountryCurrency(): bool
{
$region = wc_get_base_location();
$country = $region['country'];
$currency = get_woocommerce_currency();
if (!in_array($country, array_keys($this->allowedCountryCurrencyMatrix), true)) {
return false;
}
$applies = in_array($currency, $this->allowedCountryCurrencyMatrix[$country], true);
return $applies;
}
}

View file

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

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use Inpsyde\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use Psr\Container\ContainerInterface;
class ApplicationContextRepository
{
private $settings;
public function __construct(ContainerInterface $settings)
{
$this->settings = $settings;
}
public function currentContext(
string $shippingPreference = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING
): ApplicationContext {
$brandName = $this->settings->has('brand_name') ? $this->settings->get('brand_name') : '';
// Todo: Put user_locale in container as well?
$locale = str_replace('_', '-', get_user_locale());
$landingpage = $this->settings->has('landing_page') ?
$this->settings->get('landing_page') : ApplicationContext::LANDING_PAGE_NO_PREFERENCE;
$context = new ApplicationContext(
(string) home_url(\WC_AJAX::get_endpoint(ReturnUrlEndpoint::ENDPOINT)),
(string) wc_get_checkout_url(),
(string) $brandName,
$locale,
(string) $landingpage,
$shippingPreference
);
return $context;
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
class CartRepository implements PurchaseUnitRepositoryInterface
{
private $factory;
public function __construct(PurchaseUnitFactory $factory)
{
$this->factory = $factory;
}
/**
* Returns all Pur of the Woocommerce cart.
*
* @return PurchaseUnit[]
*/
public function all(): array
{
$cart = WC()->cart ?? new \WC_Cart();
return [$this->factory->fromWcCart($cart)];
}
}

View file

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Helper\DccApplies;
class PartnerReferralsData
{
private $merchantEmail;
private $dccApplies;
public function __construct(
string $merchantEmail,
DccApplies $dccApplies
) {
$this->merchantEmail = $merchantEmail;
$this->dccApplies = $dccApplies;
}
public function nonce(): string
{
return 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
}
public function data(): array
{
$data = $this->defaultData();
return $data;
}
private function defaultData(): array
{
return [
"email" => $this->merchantEmail,
"partner_config_override" => [
"partner_logo_url" => "https://connect.woocommerce.com/images/woocommerce_logo.png",
"return_url" => admin_url(
'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway'
),
"return_url_description" => __(
'Return to your shop.',
'woocommerce-paypal-commerce-gateway'
),
"show_add_credit_card" => true,
],
"products" => [
$this->dccApplies->forCountryCurrency() ? "PPCP" : "EXPRESS_CHECKOUT",
],
"legal_consents" => [
[
"type" => "SHARE_DATA_CONSENT",
"granted" => true,
],
],
"operations" => [
[
"operation" => "API_INTEGRATION",
"api_integration_preference" => [
"rest_api_integration" => [
"integration_method" => "PAYPAL",
"integration_type" => "FIRST_PARTY",
"first_party_details" => [
"features" => [
"PAYMENT",
"FUTURE_PAYMENT",
"REFUND",
"ADVANCED_TRANSACTIONS_SEARCH",
],
"seller_nonce" => $this->nonce(),
],
],
],
],
],
];
}
}

View file

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
//phpcs:disable Inpsyde.CodeQuality.NoAccessors.NoSetter
//phpcs:disable Inpsyde.CodeQuality.NoAccessors.NoGetter
class PayPalRequestIdRepository
{
public const KEY = 'ppcp-request-ids';
public function getForOrderId(string $orderId): string
{
$all = $this->all();
return isset($all[$orderId]) ? (string) $all[$orderId]['id'] : '';
}
public function getForOrder(Order $order): string
{
return $this->getForOrderId($order->id());
}
public function setForOrder(Order $order, string $requestId): bool
{
$all = $this->all();
$all[$order->id()] = [
'id' => $requestId,
'expiration' => time() + 10 * DAY_IN_SECONDS,
];
$all = $this->cleanup($all);
update_option(self::KEY, $all);
return true;
}
private function all(): array
{
return (array) get_option('ppcp-request-ids', []);
}
private function cleanup(array $all): array
{
foreach ($all as $orderId => $value) {
if (time() < $value['expiration']) {
continue;
}
unset($all[$orderId]);
}
return $all;
}
}

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Config\Config;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payee;
class PayeeRepository
{
private $merchantEmail;
private $merchantId;
public function __construct(string $merchantEmail, string $merchantId)
{
$this->merchantEmail = $merchantEmail;
$this->merchantId = $merchantId;
}
public function payee(): Payee
{
return new Payee(
$this->merchantEmail,
$this->merchantId
);
}
}

View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
interface PurchaseUnitRepositoryInterface
{
/**
* @return PurchaseUnit[]
*/
public function all(): array;
}

View file

@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Authentication;
use Brain\Monkey\Expectation\Exception\ExpectationArgsRequired;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Mockery;
use function Brain\Monkey\Functions\expect;
class PayPalBearerTest extends TestCase
{
public function testDefault()
{
$json = '{"access_token":"abc","expires_in":100, "created":' . time() . '}';
$cache = Mockery::mock(CacheInterface::class);
$cache
->expects('get')
->andReturn('{"access_token":"abc","expires_in":100, "created":100}');
$cache
->expects('set');
$host = 'https://example.com';
$key = 'key';
$secret = 'secret';
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$bearer = new PayPalBearer($cache, $host, $key, $secret, $logger);
expect('trailingslashit')
->with($host)
->andReturn($host . '/');
expect('wp_remote_get')
->andReturnUsing(
function ($url, $args) use ($json, $key, $secret, $host) {
if ($url !== $host . '/v1/oauth2/token?grant_type=client_credentials') {
return false;
}
if ($args['method'] !== 'POST') {
return false;
}
if ($args['headers']['Authorization'] !== 'Basic ' . base64_encode($key . ':' . $secret)) {
return false;
}
return [
'body' => $json,
];
}
);
expect('is_wp_error')
->andReturn(false);
expect('wp_remote_retrieve_response_code')
->andReturn(200);
$token = $bearer->bearer();
$this->assertEquals("abc", $token->token());
$this->assertTrue($token->isValid());
}
public function testNoTokenCached()
{
$json = '{"access_token":"abc","expires_in":100, "created":' . time() . '}';
$cache = Mockery::mock(CacheInterface::class);
$cache
->expects('get')
->andReturn('');
$cache
->expects('set');
$host = 'https://example.com';
$key = 'key';
$secret = 'secret';
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$bearer = new PayPalBearer($cache, $host, $key, $secret, $logger);
expect('trailingslashit')
->with($host)
->andReturn($host . '/');
expect('wp_remote_get')
->andReturnUsing(
function ($url, $args) use ($json, $key, $secret, $host) {
if ($url !== $host . '/v1/oauth2/token?grant_type=client_credentials') {
return false;
}
if ($args['method'] !== 'POST') {
return false;
}
if ($args['headers']['Authorization'] !== 'Basic ' . base64_encode($key . ':' . $secret)) {
return false;
}
return [
'body' => $json,
];
}
);
expect('is_wp_error')
->andReturn(false);
expect('wp_remote_retrieve_response_code')
->andReturn(200);
$token = $bearer->bearer();
$this->assertEquals("abc", $token->token());
$this->assertTrue($token->isValid());
}
public function testCachedTokenIsStillValid()
{
$json = '{"access_token":"abc","expires_in":100, "created":' . time() . '}';
$cache = Mockery::mock(CacheInterface::class);
$cache
->expects('get')
->andReturn($json);
$host = 'https://example.com';
$key = 'key';
$secret = 'secret';
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$bearer = new PayPalBearer($cache, $host, $key, $secret, $logger);
$token = $bearer->bearer();
$this->assertEquals("abc", $token->token());
$this->assertTrue($token->isValid());
}
public function testExceptionThrownOnError()
{
$json = '{"access_token":"abc","expires_in":100, "created":' . time() . '}';
$cache = Mockery::mock(CacheInterface::class);
$cache
->expects('get')
->andReturn('');
$host = 'https://example.com';
$key = 'key';
$secret = 'secret';
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$bearer = new PayPalBearer($cache, $host, $key, $secret, $logger);
expect('trailingslashit')
->with($host)
->andReturn($host . '/');
expect('wp_remote_get')
->andReturnUsing(
function ($url, $args) use ($json, $key, $secret, $host) {
if ($url !== $host . '/v1/oauth2/token?grant_type=client_credentials') {
return false;
}
if ($args['method'] !== 'POST') {
return false;
}
if ($args['headers']['Authorization'] !== 'Basic ' . base64_encode($key . ':' . $secret)) {
return false;
}
return [
'body' => $json,
];
}
);
expect('is_wp_error')
->andReturn(true);
$this->expectException(RuntimeException::class);
$bearer->bearer();
}
public function testExceptionThrownBecauseOfHttpStatusCode()
{
$json = '{"access_token":"abc","expires_in":100, "created":' . time() . '}';
$cache = Mockery::mock(CacheInterface::class);
$cache
->expects('get')
->andReturn('');
$host = 'https://example.com';
$key = 'key';
$secret = 'secret';
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$bearer = new PayPalBearer($cache, $host, $key, $secret, $logger);
expect('trailingslashit')
->with($host)
->andReturn($host . '/');
expect('wp_remote_get')
->andReturnUsing(
function ($url, $args) use ($json, $key, $secret, $host) {
if ($url !== $host . '/v1/oauth2/token?grant_type=client_credentials') {
return false;
}
if ($args['method'] !== 'POST') {
return false;
}
if ($args['headers']['Authorization'] !== 'Basic ' . base64_encode($key . ':' . $secret)) {
return false;
}
return [
'body' => $json,
];
}
);
expect('is_wp_error')
->andReturn(false);
expect('wp_remote_retrieve_response_code')
->andReturn(500);
$this->expectException(RuntimeException::class);
$bearer->bearer();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentSource;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class OrderTest extends TestCase
{
public function testOrder()
{
$id = 'id';
$createTime = new \DateTime();
$updateTime = new \DateTime();
$unit = Mockery::mock(PurchaseUnit::class);
$unit->expects('toArray')->andReturn([1]);
$status = Mockery::mock(OrderStatus::class);
$status->expects('name')->andReturn('CREATED');
$payer = Mockery::mock(Payer::class);
$payer
->expects('toArray')->andReturn(['payer']);
$intent = 'AUTHORIZE';
$applicationContext = Mockery::mock(ApplicationContext::class);
$applicationContext
->expects('toArray')
->andReturn(['applicationContext']);
$paymentSource = Mockery::mock(PaymentSource::class);
$paymentSource
->expects('toArray')
->andReturn(['paymentSource']);
$testee = new Order(
$id,
[$unit],
$status,
$applicationContext,
$paymentSource,
$payer,
$intent,
$createTime,
$updateTime
);
$this->assertEquals($id, $testee->id());
$this->assertEquals($createTime, $testee->createTime());
$this->assertEquals($updateTime, $testee->updateTime());
$this->assertEquals([$unit], $testee->purchaseUnits());
$this->assertEquals($payer, $testee->payer());
$this->assertEquals($intent, $testee->intent());
$this->assertEquals($status, $testee->status());
$expected = [
'id' => $id,
'intent' => $intent,
'status' => 'CREATED',
'purchase_units' => [
[1],
],
'create_time' => $createTime->format(\DateTimeInterface::ISO8601),
'update_time' => $updateTime->format(\DateTimeInterface::ISO8601),
'payer' => ['payer'],
'application_context' => ['applicationContext'],
'payment_source' => ['paymentSource']
];
$this->assertEquals($expected, $testee->toArray());
}
public function testOrderNoDatesOrPayer()
{
$id = 'id';
$unit = Mockery::mock(PurchaseUnit::class);
$unit->expects('toArray')->andReturn([1]);
$status = Mockery::mock(OrderStatus::class);
$status->expects('name')->andReturn('CREATED');
$testee = new Order(
$id,
[$unit],
$status
);
$this->assertEquals(null, $testee->createTime());
$this->assertEquals(null, $testee->updateTime());
$this->assertEquals(null, $testee->payer());
$this->assertEquals('CAPTURE', $testee->intent());
$array = $testee->toArray();
$this->assertFalse(array_key_exists('payer', $array));
$this->assertFalse(array_key_exists('create_time', $array));
$this->assertFalse(array_key_exists('update_time', $array));
}
}

View file

@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\Bearer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\ErrorResponseCollection;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Token;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use Inpsyde\PayPalCommerce\ApiClient\Factory\ErrorResponseCollectionFactory;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
use Psr\Log\LoggerInterface;
use function Brain\Monkey\Functions\expect;
class PaymentsEndpointTest extends TestCase
{
public function testAuthorizationDefault()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$bearer = Mockery::mock(Bearer::class);
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer
->expects('bearer')->andReturn($token);
$authorization = Mockery::mock(Authorization::class);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationFactory
->expects('fromPayPalRequest')
->andReturn($authorization);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$rawResponse = ['body' => '{"is_correct":true}'];
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturnUsing(
function ($url, $args) use ($rawResponse, $host, $authorizationId) {
if ($url !== $host . 'v2/payments/authorizations/' . $authorizationId) {
return false;
}
if ($args['headers']['Authorization'] !== 'Bearer bearer') {
return false;
}
if ($args['headers']['Content-Type'] !== 'application/json') {
return false;
}
return $rawResponse;
}
);
expect('is_wp_error')->with($rawResponse)->andReturn(false);
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(200);
$result = $testee->authorization($authorizationId);
$this->assertEquals($authorization, $result);
}
public function testAuthorizationWpError()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$rawResponse = ['body' => '{"is_correct":true}'];
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturn($rawResponse);
expect('is_wp_error')->with($rawResponse)->andReturn(true);
$this->expectException(RuntimeException::class);
$testee->authorization($authorizationId);
}
public function testAuthorizationIsNot200()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$rawResponse = ['body' => '{"some_error":true}'];
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldReceive('log');
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturn($rawResponse);
expect('is_wp_error')->with($rawResponse)->andReturn(false);
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(500);
$this->expectException(RuntimeException::class);
$testee->authorization($authorizationId);
}
public function testCaptureDefault()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer = Mockery::mock(Bearer::class);
$bearer
->expects('bearer')->andReturn($token);
$authorization = Mockery::mock(Authorization::class);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationFactory
->expects('fromPayPalRequest')
->andReturn($authorization);
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$rawResponse = ['body' => '{"is_correct":true}'];
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturnUsing(
function ($url, $args) use ($rawResponse, $host, $authorizationId) {
if ($url !== $host . 'v2/payments/authorizations/' . $authorizationId . '/capture') {
return false;
}
if ($args['method'] !== 'POST') {
return false;
}
if ($args['headers']['Authorization'] !== 'Bearer bearer') {
return false;
}
if ($args['headers']['Content-Type'] !== 'application/json') {
return false;
}
return $rawResponse;
}
);
expect('is_wp_error')->with($rawResponse)->andReturn(false);
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(201);
$result = $testee->capture($authorizationId);
$this->assertEquals($authorization, $result);
}
public function testCaptureIsWpError()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$logger = Mockery::mock(LoggerInterface::class);
$logger->expects('log');
$rawResponse = ['body' => '{"is_correct":true}'];
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturn($rawResponse);
expect('is_wp_error')->with($rawResponse)->andReturn(true);
$this->expectException(RuntimeException::class);
$testee->capture($authorizationId);
}
public function testAuthorizationIsNot201()
{
$host = 'https://example.com/';
$authorizationId = 'somekindofid';
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
$bearer = Mockery::mock(Bearer::class);
$bearer->expects('bearer')->andReturn($token);
$authorizationFactory = Mockery::mock(AuthorizationFactory::class);
$rawResponse = ['body' => '{"some_error":true}'];
$logger = Mockery::mock(LoggerInterface::class);
$logger->expects('log');
$testee = new PaymentsEndpoint(
$host,
$bearer,
$authorizationFactory,
$logger
);
expect('wp_remote_get')->andReturn($rawResponse);
expect('is_wp_error')->with($rawResponse)->andReturn(false);
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(500);
$this->expectException(RuntimeException::class);
$testee->capture($authorizationId);
}
}

View file

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\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,123 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
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
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['itemTotal']);
$shipping = Mockery::mock(Money::class);
$shipping
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['shipping']);
$taxTotal = Mockery::mock(Money::class);
$taxTotal
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['taxTotal']);
$handling = Mockery::mock(Money::class);
$handling
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['handling']);
$insurance = Mockery::mock(Money::class);
$insurance
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['insurance']);
$shippingDiscount = Mockery::mock(Money::class);
$shippingDiscount
->shouldReceive('toArray')->zeroOrMoreTimes()->andReturn(['shippingDiscount']);
$discount = Mockery::mock(Money::class);
$discount
->shouldReceive('toArray')->zeroOrMoreTimes()->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,57 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class AmountTest extends TestCase
{
public function test()
{
$money = Mockery::mock(Money::class);
$money->shouldReceive('currencyCode')->andReturn('currencyCode');
$money->shouldReceive('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->shouldReceive('currencyCode')->andReturn('currencyCode');
$money->shouldReceive('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->shouldReceive('currencyCode')->andReturn('currencyCode');
$money->shouldReceive('value')->andReturn(1.10);
$breakdown = Mockery::mock(AmountBreakdown::class);
$breakdown->shouldReceive('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 Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
class AuthorizationStatusTest extends TestCase
{
/**
* @dataProvider statusDataProvider
* @param $status
*/
public function testValidStatusProvided($status)
{
$authorizationStatus = new AuthorizationStatus($status);
$this->assertEquals($authorizationStatus->name(), $status);
}
public function testInvalidStatusProvided()
{
$this->expectException(RuntimeException::class);
new AuthorizationStatus('invalid');
}
public function testStatusComparision()
{
$authorizationStatus = new AuthorizationStatus('CREATED');
$this->assertTrue($authorizationStatus->is('CREATED'));
$this->assertFalse($authorizationStatus->is('NOT_CREATED'));
}
public function statusDataProvider(): array
{
return [
['INTERNAL'],
['CREATED'],
['CAPTURED'],
['DENIED'],
['EXPIRED'],
['PARTIALLY_CAPTURED'],
['VOIDED'],
['PENDING'],
];
}
}

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
class AuthorizationTest extends TestCase
{
public function testIdAndStatus()
{
$authorizationStatus = \Mockery::mock(AuthorizationStatus::class);
$testee = new Authorization('foo', $authorizationStatus);
$this->assertEquals('foo', $testee->id());
$this->assertEquals($authorizationStatus, $testee->status());
}
public function testToArray()
{
$authorizationStatus = \Mockery::mock(AuthorizationStatus::class);
$authorizationStatus->expects('name')->andReturn('CAPTURED');
$testee = new Authorization('foo', $authorizationStatus);
$expected = [
'id' => 'foo',
'status' => 'CAPTURED',
];
$this->assertEquals($expected, $testee->toArray());
}
}

View file

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
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,23 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\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,202 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class PayerTest extends TestCase
{
public function testPayer()
{
$birthday = new \DateTime();
$address = Mockery::mock(Address::class);
$address
->expects('toArray')
->andReturn(['address']);
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('toArray')
->andReturn(['phone']);
$taxInfo = Mockery::mock(PayerTaxInfo::class);
$taxInfo
->expects('toArray')
->andReturn(['taxInfo']);
$payerName = Mockery::mock(PayerName::class);
$payerName
->expects('toArray')
->andReturn(['payerName']);
$email = 'email@example.com';
$payerId = 'payerId';
$payer = new Payer(
$payerName,
$email,
$payerId,
$address,
$birthday,
$phone,
$taxInfo
);
$this->assertEquals($payerName, $payer->name());
$this->assertEquals($email, $payer->emailAddress());
$this->assertEquals($payerId, $payer->payerId());
$this->assertEquals($address, $payer->address());
$this->assertEquals($birthday, $payer->birthDate());
$this->assertEquals($phone, $payer->phone());
$this->assertEquals($taxInfo, $payer->taxInfo());
$array = $payer->toArray();
$this->assertEquals($birthday->format('Y-m-d'), $array['birth_date']);
$this->assertEquals(['payerName'], $array['name']);
$this->assertEquals($email, $array['email_address']);
$this->assertEquals(['address'], $array['address']);
$this->assertEquals($payerId, $array['payer_id']);
$this->assertEquals(['phone'], $array['phone']);
$this->assertEquals(['taxInfo'], $array['tax_info']);
}
public function testPayerNoId()
{
$birthday = new \DateTime();
$address = Mockery::mock(Address::class);
$address
->expects('toArray')
->andReturn(['address']);
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('toArray')
->andReturn(['phone']);
$taxInfo = Mockery::mock(PayerTaxInfo::class);
$taxInfo
->expects('toArray')
->andReturn(['taxInfo']);
$payerName = Mockery::mock(PayerName::class);
$payerName
->expects('toArray')
->andReturn(['payerName']);
$email = 'email@example.com';
$payerId = '';
$payer = new Payer(
$payerName,
$email,
$payerId,
$address,
$birthday,
$phone,
$taxInfo
);
$this->assertEquals($payerId, $payer->payerId());
$array = $payer->toArray();
$this->assertArrayNotHasKey('payer_id', $array);
}
public function testPayerNoPhone()
{
$birthday = new \DateTime();
$address = Mockery::mock(Address::class);
$address
->expects('toArray')
->andReturn(['address']);
$phone = null;
$taxInfo = Mockery::mock(PayerTaxInfo::class);
$taxInfo
->expects('toArray')
->andReturn(['taxInfo']);
$payerName = Mockery::mock(PayerName::class);
$payerName
->expects('toArray')
->andReturn(['payerName']);
$email = 'email@example.com';
$payerId = 'payerId';
$payer = new Payer(
$payerName,
$email,
$payerId,
$address,
$birthday,
$phone,
$taxInfo
);
$this->assertEquals($phone, $payer->phone());
$array = $payer->toArray();
$this->assertArrayNotHasKey('phone', $array);
}
public function testPayerNoTaxInfo()
{
$birthday = new \DateTime();
$address = Mockery::mock(Address::class);
$address
->expects('toArray')
->andReturn(['address']);
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('toArray')
->andReturn(['phone']);
$taxInfo = null;
$payerName = Mockery::mock(PayerName::class);
$payerName
->expects('toArray')
->andReturn(['payerName']);
$email = 'email@example.com';
$payerId = 'payerId';
$payer = new Payer(
$payerName,
$email,
$payerId,
$address,
$birthday,
$phone,
$taxInfo
);
$this->assertEquals($taxInfo, $payer->taxInfo());
$array = $payer->toArray();
$this->assertArrayNotHasKey('tax_info', $array);
}
public function testPayerNoBirthDate()
{
$birthday = null;
$address = Mockery::mock(Address::class);
$address
->expects('toArray')
->andReturn(['address']);
$phone = Mockery::mock(PhoneWithType::class);
$phone
->expects('toArray')
->andReturn(['phone']);
$taxInfo = Mockery::mock(PayerTaxInfo::class);
$taxInfo
->expects('toArray')
->andReturn(['taxInfo']);
$payerName = Mockery::mock(PayerName::class);
$payerName
->expects('toArray')
->andReturn(['payerName']);
$email = 'email@example.com';
$payerId = 'payerId';
$payer = new Payer(
$payerName,
$email,
$payerId,
$address,
$birthday,
$phone,
$taxInfo
);
$this->assertEquals($birthday, $payer->birthDate());
$array = $payer->toArray();
$this->assertArrayNotHasKey('birth_date', $array);
}
}

View file

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
class PaymentsTest extends TestCase
{
public function testAuthorizations()
{
$authorization = \Mockery::mock(Authorization::class);
$authorizations = [$authorization];
$testee = new Payments(...$authorizations);
$this->assertEquals($authorizations, $testee->authorizations());
}
public function testToArray()
{
$authorization = \Mockery::mock(Authorization::class);
$authorization->shouldReceive('toArray')->andReturn(
[
'id' => 'foo',
'status' => 'CREATED',
]
);
$authorizations = [$authorization];
$testee = new Payments(...$authorizations);
$this->assertEquals(
[
'authorizations' => [
[
'id' => 'foo',
'status' => 'CREATED',
],
],
],
$testee->toArray()
);
}
}

View file

@ -0,0 +1,502 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class PurchaseUnitTest extends TestCase
{
public function test()
{
$amount = Mockery::mock(
Amount::class,
[
'breakdown' => null,
'toArray' => ['amount'],
]
);
$item1 = Mockery::mock(
Item::class,
[
'toArray' => ['item1'],
'category' => Item::DIGITAL_GOODS,
]
);
$item2 = Mockery::mock(
Item::class,
[
'toArray' => ['item2'],
'category' => Item::PHYSICAL_GOODS,
]
);
$shipping = Mockery::mock(Shipping::class, ['toArray' => ['shipping']]);
$testee = new PurchaseUnit(
$amount,
[$item1, $item2],
$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([$item1, $item2], $testee->items());
self::assertTrue($testee->containsPhysicalGoodsItems());
$expected = [
'reference_id' => 'referenceId',
'amount' => ['amount'],
'description' => 'description',
'items' => [['item1'], ['item2']],
'shipping' => ['shipping'],
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
];
$this->assertEquals($expected, $testee->toArray());
}
/**
* @dataProvider dataForDitchTests
* @param array $items
* @param Amount $amount
* @param bool $doDitch
*/
public function testDitchMethod(array $items, Amount $amount, bool $doDitch, string $message)
{
$testee = new PurchaseUnit(
$amount,
$items
);
$array = $testee->toArray();
$resultItems = $doDitch === ! array_key_exists('items', $array);
$resultBreakdown = $doDitch === ! array_key_exists('breakdown', $array['amount']);
$this->assertTrue($resultItems, $message);
$this->assertTrue($resultBreakdown, $message);
}
public function dataForDitchTests() : array
{
$data = [
'default' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 26,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'dont_ditch_with_discount' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 23,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => 3,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'ditch_with_discount' => [
'message' => 'Items should be ditched because of discount.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 25,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => 3,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'dont_ditch_with_shipping_discount' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 23,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => 3,
'handling' => null,
'insurance' => null,
],
],
'ditch_with_handling' => [
'message' => 'Items should be ditched because of handling.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 26,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => 3,
'insurance' => null,
],
],
'dont_ditch_with_handling' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 29,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => 3,
'insurance' => null,
],
],
'ditch_with_insurance' => [
'message' => 'Items should be ditched because of insurance.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 26,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => 3,
],
],
'dont_ditch_with_insurance' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 29,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => 3,
],
],
'ditch_with_shipping_discount' => [
'message' => 'Items should be ditched because of shipping discount.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 25,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => 3,
'handling' => null,
'insurance' => null,
],
],
'dont_ditch_with_shipping' => [
'message' => 'Items should not be ditched.',
'ditch' => false,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 29,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => 3,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'ditch_because_shipping' => [
'message' => 'Items should be ditched because of shipping.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 28,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => 3,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'ditch_items_total' => [
'message' => 'Items should be ditched because the item total does not add up.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 26,
'breakdown' => [
'itemTotal' => 11,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'ditch_tax_total' => [
'message' => 'Items should be ditched because the tax total does not add up.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 26,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 5,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
'ditch_total_amount' => [
'message' => 'Items should be ditched because the total amount is way out of order.',
'ditch' => true,
'items' => [
[
'value' => 10,
'quantity' => 2,
'tax' => 3,
'category' => Item::PHYSICAL_GOODS,
],
],
'amount' => 260,
'breakdown' => [
'itemTotal' => 20,
'taxTotal' => 6,
'shipping' => null,
'discount' => null,
'shippingDiscount' => null,
'handling' => null,
'insurance' => null,
],
],
];
$values = [];
foreach ($data as $testKey => $test) {
$items = [];
foreach ($test['items'] as $key => $item) {
$unitAmount = Mockery::mock(Money::class);
$unitAmount->shouldReceive('value')->andReturn($item['value']);
$tax = Mockery::mock(Money::class);
$tax->shouldReceive('value')->andReturn($item['tax']);
$items[$key] = Mockery::mock(
Item::class,
[
'unitAmount' => $unitAmount,
'tax' => $tax,
'quantity'=> $item['quantity'],
'category' => $item['category'],
'toArray' => [],
]
);
}
$breakdown = null;
if ($test['breakdown']) {
$breakdown = Mockery::mock(AmountBreakdown::class);
foreach ($test['breakdown'] as $method => $value) {
$breakdown->shouldReceive($method)->andReturnUsing(function () use ($value) {
if (! is_numeric($value)) {
return null;
}
$money = Mockery::mock(Money::class);
$money->shouldReceive('value')->andReturn($value);
return $money;
});
}
}
$amount = Mockery::mock(Amount::class);
$amount->shouldReceive('toArray')->andReturn(['value' => [], 'breakdown' => []]);
$amount->shouldReceive('value')->andReturn($test['amount']);
$amount->shouldReceive('breakdown')->andReturn($breakdown);
$values[$testKey] = [
$items,
$amount,
$test['ditch'],
$test['message'],
];
}
return $values;
}
public function testPayee()
{
$amount = Mockery::mock(Amount::class);
$amount->shouldReceive('breakdown')->andReturnNull();
$amount->shouldReceive('toArray')->andReturn(['amount']);
$item1 = Mockery::mock(Item::class);
$item1->shouldReceive('toArray')->andReturn(['item1']);
$item2 = Mockery::mock(Item::class);
$item2->shouldReceive('toArray')->andReturn(['item2']);
$shipping = Mockery::mock(Shipping::class);
$shipping->shouldReceive('toArray')->andReturn(['shipping']);
$payee = Mockery::mock(Payee::class);
$payee->shouldReceive('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,137 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Entity;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
class TokenTest extends TestCase
{
/**
* @dataProvider dataForTestDefault
* @param \stdClass $data
*/
public function testDefault(\stdClass $data)
{
$token = new Token($data);
$this->assertEquals($data->token, $token->token());
$this->assertTrue($token->isValid());
}
public function dataForTestDefault() : array
{
return [
'default' => [
(object)[
'created' => time(),
'expires_in' => 100,
'token' => 'abc',
],
],
'created_not_needed' => [
(object)[
'expires_in' => 100,
'token' => 'abc',
],
],
];
}
public function testIsValid()
{
$data = (object) [
'created' => time() - 100,
'expires_in' => 99,
'token' => 'abc',
];
$token = new Token($data);
$this->assertFalse($token->isValid());
}
public function testFromBearerJson()
{
$data = json_encode([
'expires_in' => 100,
'access_token' => 'abc',
]);
$token = Token::fromJson($data);
$this->assertEquals('abc', $token->token());
$this->assertTrue($token->isValid());
}
public function testFromIdentityJson()
{
$data = json_encode([
'expires_in' => 100,
'client_token' => 'abc',
]);
$token = Token::fromJson($data);
$this->assertEquals('abc', $token->token());
$this->assertTrue($token->isValid());
}
public function testAsJson()
{
$data = (object) [
'created' => 100,
'expires_in' => 100,
'token' => 'abc',
];
$token = new Token($data);
$json = json_decode($token->asJson());
$this->assertEquals($data->token, $json->token);
$this->assertEquals($data->created, $json->created);
$this->assertEquals($data->expires_in, $json->expires_in);
}
/**
* @dataProvider dataForTestExceptions
* @param \stdClass $data
*/
public function testExceptions(\stdClass $data)
{
$this->expectException(RuntimeException::class);
new Token($data);
}
public function dataForTestExceptions() : array
{
return [
'created_is_not_integer' => [
(object) [
'created' => 'abc',
'expires_in' => 123,
'token' => 'abc',
],
],
'expires_in_is_not_integer' => [
(object) [
'expires_in' => 'abc',
'token' => 'abc',
],
],
'access_token_is_not_string' => [
(object) [
'expires_in' => 123,
'token' => ['abc'],
],
],
'access_token_does_not_exist' => [
(object) [
'expires_in' => 123,
],
],
'expires_in_does_not_exist' => [
(object) [
'token' => 'abc',
],
],
];
}
}

View file

@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class AddressFactoryTest extends TestCase
{
public function testFromWcCustomer()
{
$testee = new AddressFactory();
$customer = Mockery::mock(\WC_Customer::class);
$customer
->expects('get_shipping_country')
->andReturn('shipping_country');
$customer
->expects('get_shipping_address_1')
->andReturn('shipping_address_1');
$customer
->expects('get_shipping_address_2')
->andReturn('shipping_address_2');
$customer
->expects('get_shipping_state')
->andReturn('shipping_state');
$customer
->expects('get_shipping_city')
->andReturn('shipping_city');
$customer
->expects('get_shipping_postcode')
->andReturn('shipping_postcode');
$result = $testee->fromWcCustomer($customer);
$this->assertEquals('shipping_country', $result->countryCode());
$this->assertEquals('shipping_address_1', $result->addressLine1());
$this->assertEquals('shipping_address_2', $result->addressLine2());
$this->assertEquals('shipping_state', $result->adminArea1());
$this->assertEquals('shipping_city', $result->adminArea2());
$this->assertEquals('shipping_postcode', $result->postalCode());
}
public function testFromWcCustomersBillingAddress()
{
$testee = new AddressFactory();
$customer = Mockery::mock(\WC_Customer::class);
$customer
->expects('get_billing_country')
->andReturn('billing_country');
$customer
->expects('get_billing_address_1')
->andReturn('billing_address_1');
$customer
->expects('get_billing_address_2')
->andReturn('billing_address_2');
$customer
->expects('get_billing_state')
->andReturn('billing_state');
$customer
->expects('get_billing_city')
->andReturn('billing_city');
$customer
->expects('get_billing_postcode')
->andReturn('billing_postcode');
$result = $testee->fromWcCustomer($customer, 'billing');
$this->assertEquals('billing_country', $result->countryCode());
$this->assertEquals('billing_address_1', $result->addressLine1());
$this->assertEquals('billing_address_2', $result->addressLine2());
$this->assertEquals('billing_state', $result->adminArea1());
$this->assertEquals('billing_city', $result->adminArea2());
$this->assertEquals('billing_postcode', $result->postalCode());
}
public function testFromWcOrder()
{
$testee = new AddressFactory();
$order = Mockery::mock(\WC_Order::class);
$order
->expects('get_shipping_country')
->andReturn('shipping_country');
$order
->expects('get_shipping_address_1')
->andReturn('shipping_address_1');
$order
->expects('get_shipping_address_2')
->andReturn('shipping_address_2');
$order
->expects('get_shipping_state')
->andReturn('shipping_state');
$order
->expects('get_shipping_city')
->andReturn('shipping_city');
$order
->expects('get_shipping_postcode')
->andReturn('shipping_postcode');
$result = $testee->fromWcOrder($order);
$this->assertEquals('shipping_country', $result->countryCode());
$this->assertEquals('shipping_address_1', $result->addressLine1());
$this->assertEquals('shipping_address_2', $result->addressLine2());
$this->assertEquals('shipping_state', $result->adminArea1());
$this->assertEquals('shipping_city', $result->adminArea2());
$this->assertEquals('shipping_postcode', $result->postalCode());
}
/**
* @dataProvider dataFromPayPalRequest
*/
public function testFromPayPalRequest($data)
{
$testee = new AddressFactory();
$result = $testee->fromPayPalRequest($data);
$expectedAddressLine1 = (isset($data->address_line_1)) ? $data->address_line_1 : '';
$expectedAddressLine2 = (isset($data->address_line_2)) ? $data->address_line_2 : '';
$expectedAdminArea1 = (isset($data->admin_area_1)) ? $data->admin_area_1 : '';
$expectedAdminArea2 = (isset($data->admin_area_2)) ? $data->admin_area_2 : '';
$expectedPostalCode = (isset($data->postal_code)) ? $data->postal_code : '';
$this->assertEquals($data->country_code, $result->countryCode());
$this->assertEquals($expectedAddressLine1, $result->addressLine1());
$this->assertEquals($expectedAddressLine2, $result->addressLine2());
$this->assertEquals($expectedAdminArea1, $result->adminArea1());
$this->assertEquals($expectedAdminArea2, $result->adminArea2());
$this->assertEquals($expectedPostalCode, $result->postalCode());
}
public function testFromPayPalRequestThrowsError()
{
$testee = new AddressFactory();
$data = (object) [
'address_line_1' => 'shipping_address_1',
'address_line_2' => 'shipping_address_2',
'admin_area_1' => 'shipping_admin_area_1',
'admin_area_2' => 'shipping_admin_area_2',
'postal_code' => 'shipping_postcode',
];
$this->expectException(RuntimeException::class);
$testee->fromPayPalRequest($data);
}
public function dataFromPayPalRequest() : array
{
return [
'default' => [
(object) [
'country_code' => 'shipping_country',
'address_line_1' => 'shipping_address_1',
'address_line_2' => 'shipping_address_2',
'admin_area_1' => 'shipping_admin_area_1',
'admin_area_2' => 'shipping_admin_area_2',
'postal_code' => 'shipping_postcode',
],
],
'no_admin_area_2' => [
(object) [
'country_code' => 'shipping_country',
'address_line_1' => 'shipping_address_1',
'address_line_2' => 'shipping_address_2',
'admin_area_1' => 'shipping_admin_area_1',
'postal_code' => 'shipping_postcode',
],
],
'no_postal_code' => [
(object) [
'country_code' => 'shipping_country',
'address_line_1' => 'shipping_address_1',
'address_line_2' => 'shipping_address_2',
'admin_area_1' => 'shipping_admin_area_1',
'admin_area_2' => 'shipping_admin_area_2',
],
],
'no_admin_area_1' => [
(object) [
'country_code' => 'shipping_country',
'address_line_1' => 'shipping_address_1',
'address_line_2' => 'shipping_address_2',
'admin_area_2' => 'shipping_admin_area_2',
'postal_code' => 'shipping_postcode',
],
],
'no_address_line_1' => [
(object) [
'country_code' => 'shipping_country',
'address_line_2' => 'shipping_address_2',
'admin_area_1' => 'shipping_admin_area_1',
'admin_area_2' => 'shipping_admin_area_2',
'postal_code' => 'shipping_postcode',
],
],
'no_address_line_2' => [
(object) [
'country_code' => 'shipping_country',
'address_line_1' => 'shipping_address_1',
'admin_area_1' => 'shipping_admin_area_1',
'admin_area_2' => 'shipping_admin_area_2',
'postal_code' => 'shipping_postcode',
],
],
];
}
}

View file

@ -0,0 +1,581 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Amount;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Money;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
use function Brain\Monkey\Functions\expect;
class AmountFactoryTest extends TestCase
{
public function testFromWcCartDefault()
{
$itemFactory = Mockery::mock(ItemFactory::class);
$testee = new AmountFactory($itemFactory);
$expectedCurrency = 'EUR';
$cart = Mockery::mock(\WC_Cart::class);
$cart
->shouldReceive('get_total')
->withAnyArgs()
->andReturn(1);
$cart
->shouldReceive('get_cart_contents_total')
->andReturn(2);
$cart
->shouldReceive('get_discount_total')
->andReturn(3);
$cart
->shouldReceive('get_shipping_total')
->andReturn(4);
$cart
->shouldReceive('get_shipping_tax')
->andReturn(5);
$cart
->shouldReceive('get_cart_contents_tax')
->andReturn(6);
$cart
->shouldReceive('get_discount_tax')
->andReturn(7);
expect('get_woocommerce_currency')->andReturn($expectedCurrency);
$result = $testee->fromWcCart($cart);
$this->assertEquals($expectedCurrency, $result->currencyCode());
$this->assertEquals((float) 1, $result->value());
$this->assertEquals((float) 10, $result->breakdown()->discount()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->discount()->currencyCode());
$this->assertEquals((float) 9, $result->breakdown()->shipping()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->shipping()->currencyCode());
$this->assertEquals((float) 5, $result->breakdown()->itemTotal()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->itemTotal()->currencyCode());
$this->assertEquals((float) 13, $result->breakdown()->taxTotal()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->taxTotal()->currencyCode());
}
public function testFromWcCartNoDiscount()
{
$itemFactory = Mockery::mock(ItemFactory::class);
$testee = new AmountFactory($itemFactory);
$expectedCurrency = 'EUR';
$expectedTotal = 1;
$cart = Mockery::mock(\WC_Cart::class);
$cart
->shouldReceive('get_total')
->withAnyArgs()
->andReturn($expectedTotal);
$cart
->shouldReceive('get_cart_contents_total')
->andReturn(2);
$cart
->shouldReceive('get_discount_total')
->andReturn(0);
$cart
->shouldReceive('get_shipping_total')
->andReturn(4);
$cart
->shouldReceive('get_shipping_tax')
->andReturn(5);
$cart
->shouldReceive('get_cart_contents_tax')
->andReturn(6);
$cart
->shouldReceive('get_discount_tax')
->andReturn(0);
expect('get_woocommerce_currency')->andReturn($expectedCurrency);
$result = $testee->fromWcCart($cart);
$this->assertNull($result->breakdown()->discount());
}
public function testFromWcOrderDefault()
{
$itemFactory = Mockery::mock(ItemFactory::class);
$order = Mockery::mock(\WC_Order::class);
$unitAmount = Mockery::mock(Money::class);
$unitAmount
->shouldReceive('value')
->andReturn(3);
$tax = Mockery::mock(Money::class);
$tax
->shouldReceive('value')
->andReturn(1);
$item = Mockery::mock(Item::class);
$item
->shouldReceive('quantity')
->andReturn(2);
$item
->shouldReceive('unitAmount')
->andReturn($unitAmount);
$item
->shouldReceive('tax')
->andReturn($tax);
$itemFactory
->expects('fromWcOrder')
->with($order)
->andReturn([$item]);
$testee = new AmountFactory($itemFactory);
$expectedCurrency = 'EUR';
$order
->shouldReceive('get_total')
->andReturn(100);
$order
->shouldReceive('get_currency')
->andReturn($expectedCurrency);
$order
->shouldReceive('get_shipping_total')
->andReturn(1);
$order
->shouldReceive('get_shipping_tax')
->andReturn(.5);
$order
->shouldReceive('get_total_discount')
->with(false)
->andReturn(3);
$result = $testee->fromWcOrder($order);
$this->assertEquals((float) 3, $result->breakdown()->discount()->value());
$this->assertEquals((float) 6, $result->breakdown()->itemTotal()->value());
$this->assertEquals((float) 1.5, $result->breakdown()->shipping()->value());
$this->assertEquals((float) 100, $result->value());
$this->assertEquals((float) 2, $result->breakdown()->taxTotal()->value());
$this->assertEquals($expectedCurrency, $result->breakdown()->discount()->currencyCode());
$this->assertEquals($expectedCurrency, $result->breakdown()->itemTotal()->currencyCode());
$this->assertEquals($expectedCurrency, $result->breakdown()->shipping()->currencyCode());
$this->assertEquals($expectedCurrency, $result->breakdown()->taxTotal()->currencyCode());
$this->assertEquals($expectedCurrency, $result->currencyCode());
}
public function testFromWcOrderDiscountIsNull()
{
$itemFactory = Mockery::mock(ItemFactory::class);
$order = Mockery::mock(\WC_Order::class);
$unitAmount = Mockery::mock(Money::class);
$unitAmount
->shouldReceive('value')
->andReturn(3);
$tax = Mockery::mock(Money::class);
$tax
->shouldReceive('value')
->andReturn(1);
$item = Mockery::mock(Item::class);
$item
->shouldReceive('quantity')
->andReturn(2);
$item
->shouldReceive('unitAmount')
->andReturn($unitAmount);
$item
->shouldReceive('tax')
->andReturn($tax);
$itemFactory
->expects('fromWcOrder')
->with($order)
->andReturn([$item]);
$testee = new AmountFactory($itemFactory);
$expectedCurrency = 'EUR';
$order
->shouldReceive('get_total')
->andReturn(100);
$order
->shouldReceive('get_currency')
->andReturn($expectedCurrency);
$order
->shouldReceive('get_shipping_total')
->andReturn(1);
$order
->shouldReceive('get_shipping_tax')
->andReturn(.5);
$order
->shouldReceive('get_total_discount')
->with(false)
->andReturn(0);
$result = $testee->fromWcOrder($order);
$this->assertNull($result->breakdown()->discount());
}
/**
* @dataProvider dataFromPayPalResponse
* @param $response
*/
public function testFromPayPalResponse($response, $expectsException)
{
$itemFactory = Mockery::mock(ItemFactory::class);
$testee = new AmountFactory($itemFactory);
if ($expectsException) {
$this->expectException(RuntimeException::class);
}
$result = $testee->fromPayPalResponse($response);
if ($expectsException) {
return;
}
$this->assertEquals($response->value, $result->value());
$this->assertEquals($response->currency_code, $result->currencyCode());
$breakdown = $result->breakdown();
if (! isset($response->breakdown)) {
$this->assertNull($breakdown);
return;
}
if ($breakdown->shipping()) {
$this->assertEquals($response->breakdown->shipping->value, $breakdown->shipping()->value());
$this->assertEquals($response->breakdown->shipping->currency_code, $breakdown->shipping()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->shipping));
}
if ($breakdown->itemTotal()) {
$this->assertEquals($response->breakdown->item_total->value, $breakdown->itemTotal()->value());
$this->assertEquals($response->breakdown->item_total->currency_code, $breakdown->itemTotal()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->item_total));
}
if ($breakdown->taxTotal()) {
$this->assertEquals($response->breakdown->tax_total->value, $breakdown->taxTotal()->value());
$this->assertEquals($response->breakdown->tax_total->currency_code, $breakdown->taxTotal()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->tax_total));
}
if ($breakdown->handling()) {
$this->assertEquals($response->breakdown->handling->value, $breakdown->handling()->value());
$this->assertEquals($response->breakdown->handling->currency_code, $breakdown->handling()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->handling));
}
if ($breakdown->insurance()) {
$this->assertEquals($response->breakdown->insurance->value, $breakdown->insurance()->value());
$this->assertEquals($response->breakdown->insurance->currency_code, $breakdown->insurance()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->insurance));
}
if ($breakdown->shippingDiscount()) {
$this->assertEquals($response->breakdown->shipping_discount->value, $breakdown->shippingDiscount()->value());
$this->assertEquals($response->breakdown->shipping_discount->currency_code, $breakdown->shippingDiscount()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->shipping_discount));
}
if ($breakdown->discount()) {
$this->assertEquals($response->breakdown->discount->value, $breakdown->discount()->value());
$this->assertEquals($response->breakdown->discount->currency_code, $breakdown->discount()->currencyCode());
} else {
$this->assertTrue(! isset($response->breakdown->discount));
}
}
public function dataFromPayPalResponse() : array
{
return [
'no_value' => [
(object) [
"currency_code" => "A",
],
true,
],
'no_currency_code' => [
(object) [
"value" => (float) 1,
],
true,
],
'no_value_in_breakdown' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"currency_code" => "B",
],
],
],
true,
],
'no_currency_code_in_breakdown' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
],
],
],
true,
],
'default' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_item_total' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
],
],
false,
],
'no_tax_total' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_handling' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_insurance' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_shipping_discount' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_discount' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"shipping" => (object) [
"value" => (float) 7,
"currency_code" => "G",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
'no_shipping' => [
(object) [
"value" => (float) 1,
"currency_code" => "A",
"breakdown" => (object) [
"discount" => (object) [
"value" => (float) 2,
"currency_code" => "B",
],
"shipping_discount" => (object) [
"value" => (float) 3,
"currency_code" => "C",
],
"insurance" => (object) [
"value" => (float) 4,
"currency_code" => "D",
],
"handling" => (object) [
"value" => (float) 5,
"currency_code" => "E",
],
"tax_total" => (object) [
"value" => (float) 6,
"currency_code" => "F",
],
"item_total" => (object) [
"value" => (float) 8,
"currency_code" => "H",
],
],
],
false,
],
];
}
}

View file

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
class AuthorizationFactoryTest extends TestCase
{
public function testFromPayPalRequestDefault()
{
$response = (object)[
'id' => 'foo',
'status' => 'CAPTURED',
];
$testee = new AuthorizationFactory();
$result = $testee->fromPayPalRequest($response);
$this->assertInstanceOf(Authorization::class, $result);
$this->assertEquals('foo', $result->id());
$this->assertInstanceOf(AuthorizationStatus::class, $result->status());
$this->assertEquals('CAPTURED', $result->status()->name());
}
public function testReturnExceptionIdIsMissing()
{
$this->expectException(RuntimeException::class);
$response = (object)[
'status' => 'CAPTURED',
];
$testee = new AuthorizationFactory();
$testee->fromPayPalRequest($response);
}
public function testReturnExceptionStatusIsMissing()
{
$this->expectException(RuntimeException::class);
$response = (object)[
'id' => 'foo',
];
$testee = new AuthorizationFactory();
$testee->fromPayPalRequest($response);
}
}

View file

@ -0,0 +1,420 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use function Brain\Monkey\Functions\expect;
use Mockery;
class ItemFactoryTest extends TestCase
{
public function testFromCartDefault()
{
$testee = new ItemFactory();
$product = Mockery::mock(\WC_Product_Simple::class);
$product
->expects('get_name')
->andReturn('name');
$product
->expects('get_description')
->andReturn('description');
$product
->expects('get_sku')
->andReturn('sku');
$product
->expects('is_virtual')
->andReturn(false);
$items = [
[
'data' => $product,
'quantity' => 1,
],
];
$cart = Mockery::mock(\WC_Cart::class);
$cart
->expects('get_cart_contents')
->andReturn($items);
expect('get_woocommerce_currency')
->andReturn('EUR');
expect('wc_get_price_including_tax')
->with($product)
->andReturn(2.995);
expect('wc_get_price_excluding_tax')
->with($product)
->andReturn(1);
$result = $testee->fromWcCart($cart);
$this->assertCount(1, $result);
$item = current($result);
$this->assertInstanceOf(Item::class, $item);
/**
* @var Item $item
*/
$this->assertEquals(Item::PHYSICAL_GOODS, $item->category());
$this->assertEquals('description', $item->description());
$this->assertEquals(1, $item->quantity());
$this->assertEquals('name', $item->name());
$this->assertEquals('sku', $item->sku());
$this->assertEquals(1, $item->unitAmount()->value());
$this->assertEquals(2, $item->tax()->value());
}
public function testFromCartDigitalGood()
{
$testee = new ItemFactory();
$product = Mockery::mock(\WC_Product_Simple::class);
$product
->expects('get_name')
->andReturn('name');
$product
->expects('get_description')
->andReturn('description');
$product
->expects('get_sku')
->andReturn('sku');
$product
->expects('is_virtual')
->andReturn(true);
$items = [
[
'data' => $product,
'quantity' => 1,
],
];
$cart = Mockery::mock(\WC_Cart::class);
$cart
->expects('get_cart_contents')
->andReturn($items);
expect('get_woocommerce_currency')
->andReturn('EUR');
expect('wc_get_price_including_tax')
->with($product)
->andReturn(2.995);
expect('wc_get_price_excluding_tax')
->with($product)
->andReturn(1);
$result = $testee->fromWcCart($cart);
$item = current($result);
$this->assertEquals(Item::DIGITAL_GOODS, $item->category());
}
public function testFromWcOrderDefault()
{
$testee = new ItemFactory();
$product = Mockery::mock(\WC_Product::class);
$product
->expects('get_name')
->andReturn('name');
$product
->expects('get_description')
->andReturn('description');
$product
->expects('get_sku')
->andReturn('sku');
$product
->expects('is_virtual')
->andReturn(false);
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
->expects('get_product')
->andReturn($product);
$item
->expects('get_quantity')
->andReturn(1);
$order = Mockery::mock(\WC_Order::class);
$order
->expects('get_currency')
->andReturn('EUR');
$order
->expects('get_items')
->andReturn([$item]);
$order
->expects('get_item_subtotal')
->with($item, true)
->andReturn(3);
$order
->expects('get_item_subtotal')
->with($item, false)
->andReturn(1);
$result = $testee->fromWcOrder($order);
$this->assertCount(1, $result);
$item = current($result);
/**
* @var Item $item
*/
$this->assertInstanceOf(Item::class, $item);
$this->assertEquals('name', $item->name());
$this->assertEquals('description', $item->description());
$this->assertEquals(1, $item->quantity());
$this->assertEquals(Item::PHYSICAL_GOODS, $item->category());
$this->assertEquals(1, $item->unitAmount()->value());
$this->assertEquals(2, $item->tax()->value());
}
public function testFromWcOrderDigitalGood()
{
$testee = new ItemFactory();
$product = Mockery::mock(\WC_Product::class);
$product
->expects('get_name')
->andReturn('name');
$product
->expects('get_description')
->andReturn('description');
$product
->expects('get_sku')
->andReturn('sku');
$product
->expects('is_virtual')
->andReturn(true);
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
->expects('get_product')
->andReturn($product);
$item
->expects('get_quantity')
->andReturn(1);
$order = Mockery::mock(\WC_Order::class);
$order
->expects('get_currency')
->andReturn('EUR');
$order
->expects('get_items')
->andReturn([$item]);
$order
->expects('get_item_subtotal')
->with($item, true)
->andReturn(3);
$order
->expects('get_item_subtotal')
->with($item, false)
->andReturn(1);
$result = $testee->fromWcOrder($order);
$item = current($result);
/**
* @var Item $item
*/
$this->assertEquals(Item::DIGITAL_GOODS, $item->category());
}
public function testFromWcOrderMaxStringLength()
{
$name = 'öawjetöagrjjaglörjötairgjaflkögjöalfdgjöalfdjblköajtlkfjdbljslkgjfklösdgjalkerjtlrajglkfdajblköajflköbjsdgjadfgjaöfgjaölkgjkladjgfköajgjaflgöjafdlgjafdögjdsflkgjö4jwegjfsdbvxj öskögjtaeröjtrgt';
$description = 'öawjetöagrjjaglörjötairgjaflkögjöalfdgjöalfdjblköajtlkfjdbljslkgjfklösdgjalkerjtlrajglkfdajblköajflköbjsdgjadfgjaöfgjaölkgjkladjgfköajgjaflgöjafdlgjafdögjdsflkgjö4jwegjfsdbvxj öskögjtaeröjtrgt';
$testee = new ItemFactory();
$product = Mockery::mock(\WC_Product::class);
$product
->expects('get_name')
->andReturn($name);
$product
->expects('get_description')
->andReturn($description);
$product
->expects('get_sku')
->andReturn('sku');
$product
->expects('is_virtual')
->andReturn(true);
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
->expects('get_product')
->andReturn($product);
$item
->expects('get_quantity')
->andReturn(1);
$order = Mockery::mock(\WC_Order::class);
$order
->expects('get_currency')
->andReturn('EUR');
$order
->expects('get_items')
->andReturn([$item]);
$order
->expects('get_item_subtotal')
->with($item, true)
->andReturn(3);
$order
->expects('get_item_subtotal')
->with($item, false)
->andReturn(1);
$result = $testee->fromWcOrder($order);
$item = current($result);
/**
* @var Item $item
*/
$this->assertEquals(mb_substr($name, 0, 127), $item->name());
$this->assertEquals(mb_substr($description, 0, 127), $item->description());
}
public function testFromPayPalResponse()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'quantity' => 1,
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
];
$item = $testee->fromPayPalResponse($response);
$this->assertInstanceOf(Item::class, $item);
/**
* @var Item $item
*/
$this->assertInstanceOf(Item::class, $item);
$this->assertEquals('name', $item->name());
$this->assertEquals('description', $item->description());
$this->assertEquals(1, $item->quantity());
$this->assertEquals(Item::PHYSICAL_GOODS, $item->category());
$this->assertEquals(1, $item->unitAmount()->value());
$this->assertNull($item->tax());
}
public function testFromPayPalResponseDigitalGood()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'quantity' => 1,
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
'category' => Item::DIGITAL_GOODS,
];
$item = $testee->fromPayPalResponse($response);
/**
* @var Item $item
*/
$this->assertEquals(Item::DIGITAL_GOODS, $item->category());
}
public function testFromPayPalResponseHasTax()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'quantity' => 1,
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
'tax' => (object) [
'value' => 100,
'currency_code' => 'EUR',
],
];
$item = $testee->fromPayPalResponse($response);
$this->assertEquals(100, $item->tax()->value());
}
public function testFromPayPalResponseThrowsWithoutName()
{
$testee = new ItemFactory();
$response = (object) [
'description' => 'description',
'quantity' => 1,
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
'tax' => (object) [
'value' => 100,
'currency_code' => 'EUR',
],
];
$this->expectException(RuntimeException::class);
$testee->fromPayPalResponse($response);
}
public function testFromPayPalResponseThrowsWithoutQuantity()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
'tax' => (object) [
'value' => 100,
'currency_code' => 'EUR',
],
];
$this->expectException(RuntimeException::class);
$testee->fromPayPalResponse($response);
}
public function testFromPayPalResponseThrowsWithStringInQuantity()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'quantity' => 'should-not-be-a-string',
'unit_amount' => (object) [
'value' => 1,
'currency_code' => 'EUR',
],
'tax' => (object) [
'value' => 100,
'currency_code' => 'EUR',
],
];
$this->expectException(RuntimeException::class);
$testee->fromPayPalResponse($response);
}
public function testFromPayPalResponseThrowsWithWrongUnitAmount()
{
$testee = new ItemFactory();
$response = (object) [
'name' => 'name',
'description' => 'description',
'quantity' => 1,
'unit_amount' => (object) [
],
'tax' => (object) [
'value' => 100,
'currency_code' => 'EUR',
],
];
$this->expectException(RuntimeException::class);
$testee->fromPayPalResponse($response);
}
}

View file

@ -0,0 +1,221 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payer;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentSource;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class OrderFactoryTest extends TestCase
{
public function testFromWcOrder()
{
$createTime = new \DateTime();
$updateTime = new \DateTime();
$payer = Mockery::mock(Payer::class);
$status = Mockery::mock(OrderStatus::class);
$order = Mockery::mock(Order::class);
$order->expects('id')->andReturn('id');
$order->expects('status')->andReturn($status);
$order->expects('payer')->andReturn($payer);
$order->expects('intent')->andReturn('intent');
$order->expects('createTime')->andReturn($createTime);
$order->expects('updateTime')->andReturn($updateTime);
$order->expects('applicationContext')->andReturnNull();
$order->expects('paymentSource')->andReturnNull();
$wcOrder = Mockery::mock(\WC_Order::class);
$purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnitFactory->expects('fromWcOrder')->with($wcOrder)->andReturn($purchaseUnit);
$payerFactory = Mockery::mock(PayerFactory::class);
$applicationRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationFactory = Mockery::mock(ApplicationContextFactory::class);
$paymentSourceFactory = Mockery::mock(PaymentSourceFactory::class);
$testee = new OrderFactory(
$purchaseUnitFactory,
$payerFactory,
$applicationRepository,
$applicationFactory,
$paymentSourceFactory
);
$result = $testee->fromWcOrder($wcOrder, $order);
$resultPurchaseUnit = current($result->purchaseUnits());
$this->assertEquals($purchaseUnit, $resultPurchaseUnit);
}
/**
* @dataProvider dataForTestFromPayPalResponseTest
* @param $orderData
*/
public function testFromPayPalResponse($orderData)
{
$purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
if (count($orderData->purchase_units)) {
$purchaseUnitFactory
->expects('fromPayPalResponse')
->times(count($orderData->purchase_units))
->andReturn(Mockery::mock(PurchaseUnit::class));
}
$payerFactory = Mockery::mock(PayerFactory::class);
if (isset($orderData->payer)) {
$payerFactory
->expects('fromPayPalResponse')
->andReturn(Mockery::mock(Payer::class));
}
$applicationRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationFactory = Mockery::mock(ApplicationContextFactory::class);
$paymentSourceFactory = Mockery::mock(PaymentSourceFactory::class);
$testee = new OrderFactory(
$purchaseUnitFactory,
$payerFactory,
$applicationRepository,
$applicationFactory,
$paymentSourceFactory
);
$order = $testee->fromPayPalResponse($orderData);
$this->assertCount(count($orderData->purchase_units), $order->purchaseUnits());
$this->assertEquals($orderData->id, $order->id());
$this->assertEquals($orderData->status, $order->status()->name());
$this->assertEquals($orderData->intent, $order->intent());
if (! isset($orderData->create_time)) {
$this->assertNull($order->createTime());
} else {
$this->assertEquals($orderData->create_time, $order->createTime()->format(\DateTime::ISO8601));
}
if (! isset($orderData->payer)) {
$this->assertNull($order->payer());
} else {
$this->assertInstanceOf(Payer::class, $order->payer());
}
if (! isset($orderData->update_time)) {
$this->assertNull($order->updateTime());
} else {
$this->assertEquals($orderData->update_time, $order->updateTime()->format(\DateTime::ISO8601));
}
}
public function dataForTestFromPayPalResponseTest() : array
{
return [
'default' => [
(object) [
'id' => 'id',
'purchase_units' => [new \stdClass(), new \stdClass()],
'status' => OrderStatus::APPROVED,
'intent' => 'CAPTURE',
'create_time' => '2005-08-15T15:52:01+0000',
'update_time' => '2005-09-15T15:52:01+0000',
'payer' => new \stdClass(),
],
],
'no_update_time' => [
(object) [
'id' => 'id',
'purchase_units' => [new \stdClass(), new \stdClass()],
'status' => OrderStatus::APPROVED,
'intent' => 'CAPTURE',
'create_time' => '2005-08-15T15:52:01+0000',
'payer' => new \stdClass(),
],
],
'no_create_time' => [
(object) [
'id' => 'id',
'purchase_units' => [new \stdClass(), new \stdClass()],
'status' => OrderStatus::APPROVED,
'intent' => 'CAPTURE',
'update_time' => '2005-09-15T15:52:01+0000',
'payer' => new \stdClass(),
],
],
'no_payer' => [
(object) [
'id' => 'id',
'purchase_units' => [new \stdClass(), new \stdClass()],
'status' => OrderStatus::APPROVED,
'intent' => 'CAPTURE',
'create_time' => '2005-08-15T15:52:01+0000',
'update_time' => '2005-09-15T15:52:01+0000',
],
],
];
}
/**
* @dataProvider dataForTestFromPayPalResponseExceptionsTest
* @param $orderData
*/
public function testFromPayPalResponseExceptions($orderData)
{
$purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
$payerFactory = Mockery::mock(PayerFactory::class);
$applicationRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationFactory = Mockery::mock(ApplicationContextFactory::class);
$paymentSourceFactory = Mockery::mock(PaymentSourceFactory::class);
$testee = new OrderFactory(
$purchaseUnitFactory,
$payerFactory,
$applicationRepository,
$applicationFactory,
$paymentSourceFactory
);
$this->expectException(RuntimeException::class);
$testee->fromPayPalResponse($orderData);
}
public function dataForTestFromPayPalResponseExceptionsTest() : array
{
return [
'no_id' => [
(object) [
'purchase_units' => [],
'status' => '',
'intent' => '',
],
],
'no_purchase_units' => [
(object) [
'id' => '',
'status' => '',
'intent' => '',
],
],
'purchase_units_is_not_array' => [
(object) [
'id' => '',
'purchase_units' => 1,
'status' => '',
'intent' => '',
],
],
'no_status' => [
(object) [
'id' => '',
'purchase_units' => [],
'intent' => '',
],
],
'no_intent' => [
(object) [
'id' => '',
'purchase_units' => [],
'status' => '',
],
],
];
}
}

View file

@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Address;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class PayerFactoryTest extends TestCase
{
public function testFromWcCustomer()
{
$expectedPhone = '012345678901';
$expectedEmail = 'test@example.com';
$expectedFirstName = 'John';
$expectedLastName = 'Locke';
$address = Mockery::mock(Address::class);
$customer = Mockery::mock(\WC_Customer::class);
$customer
->shouldReceive('get_billing_phone')
->andReturn($expectedPhone);
$customer
->shouldReceive('get_billing_email')
->andReturn($expectedEmail);
$customer
->shouldReceive('get_billing_last_name')
->andReturn($expectedLastName);
$customer
->shouldReceive('get_billing_first_name')
->andReturn($expectedFirstName);
$addressFactory = Mockery::mock(AddressFactory::class);
$addressFactory
->expects('fromWcCustomer')
->with($customer, 'billing')
->andReturn($address);
$testee = new PayerFactory($addressFactory);
$result = $testee->fromCustomer($customer);
$this->assertEquals($expectedEmail, $result->emailAddress());
$this->assertEquals($expectedLastName, $result->name()->surname());
$this->assertEquals($expectedFirstName, $result->name()->givenName());
$this->assertEquals($address, $result->address());
$this->assertEquals($expectedPhone, $result->phone()->phone()->nationalNumber());
$this->assertNull($result->birthDate());
$this->assertEmpty($result->payerId());
}
/**
* The phone number is only allowed to contain numbers.
* The WC_Customer get_billing_phone can contain other characters, which need to
* get stripped.
*/
public function testFromWcCustomerStringsFromNumberAreRemoved()
{
$expectedPhone = '012345678901';
$expectedEmail = 'test@example.com';
$expectedFirstName = 'John';
$expectedLastName = 'Locke';
$address = Mockery::mock(Address::class);
$customer = Mockery::mock(\WC_Customer::class);
$customer
->shouldReceive('get_billing_phone')
->andReturn($expectedPhone . 'abcdefg');
$customer
->shouldReceive('get_billing_email')
->andReturn($expectedEmail);
$customer
->shouldReceive('get_billing_last_name')
->andReturn($expectedLastName);
$customer
->shouldReceive('get_billing_first_name')
->andReturn($expectedFirstName);
$addressFactory = Mockery::mock(AddressFactory::class);
$addressFactory
->expects('fromWcCustomer')
->with($customer, 'billing')
->andReturn($address);
$testee = new PayerFactory($addressFactory);
$result = $testee->fromCustomer($customer);
$this->assertEquals($expectedPhone, $result->phone()->phone()->nationalNumber());
}
public function testFromWcCustomerNoNumber()
{
$expectedEmail = 'test@example.com';
$expectedFirstName = 'John';
$expectedLastName = 'Locke';
$address = Mockery::mock(Address::class);
$customer = Mockery::mock(\WC_Customer::class);
$customer
->shouldReceive('get_billing_phone')
->andReturn('');
$customer
->shouldReceive('get_billing_email')
->andReturn($expectedEmail);
$customer
->shouldReceive('get_billing_last_name')
->andReturn($expectedLastName);
$customer
->shouldReceive('get_billing_first_name')
->andReturn($expectedFirstName);
$addressFactory = Mockery::mock(AddressFactory::class);
$addressFactory
->expects('fromWcCustomer')
->with($customer, 'billing')
->andReturn($address);
$testee = new PayerFactory($addressFactory);
$result = $testee->fromCustomer($customer);
$this->assertNull($result->phone());
}
/**
* The phone number is not allowed to be longer than 14 characters.
* We need to make sure, we strip the number if longer.
*/
public function testFromWcCustomerTooLongNumberGetsStripped()
{
$expectedPhone = '01234567890123';
$expectedEmail = 'test@example.com';
$expectedFirstName = 'John';
$expectedLastName = 'Locke';
$address = Mockery::mock(Address::class);
$customer = Mockery::mock(\WC_Customer::class);
$customer
->shouldReceive('get_billing_phone')
->andReturn($expectedPhone . '456789');
$customer
->shouldReceive('get_billing_email')
->andReturn($expectedEmail);
$customer
->shouldReceive('get_billing_last_name')
->andReturn($expectedLastName);
$customer
->shouldReceive('get_billing_first_name')
->andReturn($expectedFirstName);
$addressFactory = Mockery::mock(AddressFactory::class);
$addressFactory
->expects('fromWcCustomer')
->with($customer, 'billing')
->andReturn($address);
$testee = new PayerFactory($addressFactory);
$result = $testee->fromCustomer($customer);
$this->assertEquals($expectedPhone, $result->phone()->phone()->nationalNumber());
}
/**
* @dataProvider dataForTestFromPayPalResponse
*/
public function testFromPayPalResponse($data)
{
$addressFactory = Mockery::mock(AddressFactory::class);
$addressFactory
->expects('fromPayPalRequest')
->with($data->address)
->andReturn(Mockery::mock(Address::class));
$testee = new PayerFactory($addressFactory);
$payer = $testee->fromPayPalResponse($data);
$this->assertEquals($data->email_address, $payer->emailAddress());
$this->assertEquals($data->payer_id, $payer->payerId());
$this->assertEquals($data->name->given_name, $payer->name()->givenName());
$this->assertEquals($data->name->surname, $payer->name()->surname());
if (isset($data->phone)) {
$this->assertEquals($data->phone->phone_type, $payer->phone()->type());
$this->assertEquals($data->phone->phone_number->national_number, $payer->phone()->phone()->nationalNumber());
} else {
$this->assertNull($payer->phone());
}
$this->assertInstanceOf(Address::class, $payer->address());
if (isset($data->tax_info)) {
$this->assertEquals($data->tax_info->tax_id, $payer->taxInfo()->taxId());
$this->assertEquals($data->tax_info->tax_id_type, $payer->taxInfo()->type());
} else {
$this->assertNull($payer->taxInfo());
}
if (isset($data->birth_date)) {
$this->assertEquals($data->birth_date, $payer->birthDate()->format('Y-m-d'));
} else {
$this->assertNull($payer->birthDate());
}
}
public function dataForTestFromPayPalResponse() : array
{
return [
'default' => [
(object)[
'address' => new \stdClass(),
'name' => (object)[
'given_name' => 'given_name',
'surname' => 'surname',
],
'phone' => (object)[
'phone_type' => 'HOME',
'phone_number' => (object)[
'national_number' => '1234567890',
],
],
'tax_info' => (object)[
'tax_id' => 'tax_id',
'tax_id_type' => 'BR_CPF',
],
'birth_date' => '1970-01-01',
'email_address' => 'email_address',
'payer_id' => 'payer_id',
],
],
'no_phone' => [
(object)[
'address' => new \stdClass(),
'name' => (object)[
'given_name' => 'given_name',
'surname' => 'surname',
],
'tax_info' => (object)[
'tax_id' => 'tax_id',
'tax_id_type' => 'BR_CPF',
],
'birth_date' => '1970-01-01',
'email_address' => 'email_address',
'payer_id' => 'payer_id',
],
],
'no_tax_info' => [
(object)[
'address' => new \stdClass(),
'name' => (object)[
'given_name' => 'given_name',
'surname' => 'surname',
],
'phone' => (object)[
'phone_type' => 'HOME',
'phone_number' => (object)[
'national_number' => '1234567890',
],
],
'birth_date' => '1970-01-01',
'email_address' => 'email_address',
'payer_id' => 'payer_id',
],
],
'no_birth_date' => [
(object)[
'address' => new \stdClass(),
'name' => (object)[
'given_name' => 'given_name',
'surname' => 'surname',
],
'phone' => (object)[
'phone_type' => 'HOME',
'phone_number' => (object)[
'national_number' => '1234567890',
],
],
'tax_info' => (object)[
'tax_id' => 'tax_id',
'tax_id_type' => 'BR_CPF',
],
'email_address' => 'email_address',
'payer_id' => 'payer_id',
],
],
];
}
}

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payments;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class PaymentsFactoryTest extends TestCase
{
public function testFromPayPalResponse()
{
$authorization = Mockery::mock(Authorization::class);
$authorization->shouldReceive('toArray')->andReturn(['id' => 'foo', 'status' => 'CREATED']);
$authorizationsFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationsFactory->shouldReceive('fromPayPalRequest')->andReturn($authorization);
$response = (object)[
'authorizations' => [
(object)['id' => 'foo', 'status' => 'CREATED'],
],
];
$testee = new PaymentsFactory($authorizationsFactory);
$result = $testee->fromPayPalResponse($response);
$this->assertInstanceOf(Payments::class, $result);
$expectedToArray = [
'authorizations' => [
['id' => 'foo', 'status' => 'CREATED'],
],
];
$this->assertEquals($expectedToArray, $result->toArray());
}
}

View file

@ -0,0 +1,659 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Factory;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Address;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Amount;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payee;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Payments;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Shipping;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
use function Brain\Monkey\Functions\expect;
class PurchaseUnitFactoryTest extends TestCase
{
public function testWcOrderDefault()
{
$wcOrderId = 1;
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder
->expects('get_id')->andReturn($wcOrderId);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->shouldReceive('fromWcOrder')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->shouldReceive('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->shouldReceive('fromWcOrder')
->with($wcOrder)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->shouldReceive('countryCode')
->twice()
->andReturn('DE');
$address
->shouldReceive('postalCode')
->andReturn('12345');
$shipping = Mockery::mock(Shipping::class);
$shipping
->shouldReceive('address')
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->shouldReceive('fromWcOrder')
->with($wcOrder)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcOrder($wcOrder);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->referenceId());
$this->assertEquals('WC-' . $wcOrderId, $unit->customId());
$this->assertEquals('', $unit->softDescriptor());
$this->assertEquals('WC-' . $wcOrderId, $unit->invoiceId());
$this->assertEquals([], $unit->items());
$this->assertEquals($amount, $unit->amount());
$this->assertEquals($shipping, $unit->shipping());
}
public function testWcOrderShippingGetsDroppedWhenNoPostalCode()
{
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder
->expects('get_id')->andReturn(1);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->expects('countryCode')
->twice()
->andReturn('DE');
$address
->expects('postalCode')
->andReturn('');
$shipping = Mockery::mock(Shipping::class);
$shipping
->expects('address')
->times(3)
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcOrder($wcOrder);
$this->assertEquals(null, $unit->shipping());
}
public function testWcOrderShippingGetsDroppedWhenNoCountryCode()
{
$wcOrder = Mockery::mock(\WC_Order::class);
$wcOrder
->expects('get_id')->andReturn(1);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->expects('countryCode')
->andReturn('');
$shipping = Mockery::mock(Shipping::class);
$shipping
->expects('address')
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('fromWcOrder')
->with($wcOrder)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcOrder($wcOrder);
$this->assertEquals(null, $unit->shipping());
}
public function testWcCartDefault()
{
$wcCustomer = Mockery::mock(\WC_Customer::class);
expect('WC')
->andReturn((object) ['customer' => $wcCustomer]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->shouldReceive('countryCode')
->andReturn('DE');
$address
->shouldReceive('postalCode')
->andReturn('12345');
$shipping = Mockery::mock(Shipping::class);
$shipping
->shouldReceive('address')
->zeroOrMoreTimes()
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('fromWcCustomer')
->with($wcCustomer)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcCart($wcCart);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->referenceId());
$this->assertEquals('', $unit->customId());
$this->assertEquals('', $unit->softDescriptor());
$this->assertEquals('', $unit->invoiceId());
$this->assertEquals([], $unit->items());
$this->assertEquals($amount, $unit->amount());
$this->assertEquals($shipping, $unit->shipping());
}
public function testWcCartShippingGetsDroppendWhenNoCustomer()
{
expect('WC')
->andReturn((object) ['customer' => null]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn([]);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcCart($wcCart);
$this->assertNull($unit->shipping());
}
public function testWcCartShippingGetsDroppendWhenNoPostalCode()
{
expect('WC')
->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class)]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->shouldReceive('countryCode')
->andReturn('DE');
$address
->shouldReceive('postalCode')
->andReturn('');
$shipping = Mockery::mock(Shipping::class);
$shipping
->shouldReceive('address')
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('fromWcCustomer')
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcCart($wcCart);
$this->assertNull($unit->shipping());
}
public function testWcCartShippingGetsDroppendWhenNoCountryCode()
{
expect('WC')
->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class)]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
$amountFactory = Mockery::mock(AmountFactory::class);
$amountFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$payee = Mockery::mock(Payee::class);
$payeeRepository
->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('fromWcCart')
->with($wcCart)
->andReturn([]);
$address = Mockery::mock(Address::class);
$address
->shouldReceive('countryCode')
->andReturn('');
$shipping = Mockery::mock(Shipping::class);
$shipping
->shouldReceive('address')
->andReturn($address);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('fromWcCustomer')
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$unit = $testee->fromWcCart($wcCart);
$this->assertNull($unit->shipping());
}
public function testFromPayPalResponseDefault()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$rawShipping = (object) ['shipping' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('fromPayPalResponse')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('fromPayPalResponse')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('fromPayPalResponse')->with($rawItem)->andReturn($item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shipping = Mockery::mock(Shipping::class);
$shippingFactory->expects('fromPayPalResponse')->with($rawShipping)->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$response = (object) [
'reference_id' => 'default',
'description' => 'description',
'custom_id' => 'customId',
'invoice_id' => 'invoiceId',
'soft_descriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
];
$unit = $testee->fromPayPalResponse($response);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
$this->assertEquals($payee, $unit->payee());
$this->assertEquals('description', $unit->description());
$this->assertEquals('default', $unit->referenceId());
$this->assertEquals('customId', $unit->customId());
$this->assertEquals('softDescriptor', $unit->softDescriptor());
$this->assertEquals('invoiceId', $unit->invoiceId());
$this->assertEquals([$item], $unit->items());
$this->assertEquals($amount, $unit->amount());
$this->assertEquals($shipping, $unit->shipping());
}
public function testFromPayPalResponsePayeeIsNull()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$rawShipping = (object) ['shipping' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('fromPayPalResponse')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('fromPayPalResponse')->with($rawItem)->andReturn($item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shipping = Mockery::mock(Shipping::class);
$shippingFactory->expects('fromPayPalResponse')->with($rawShipping)->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$response = (object) [
'reference_id' => 'default',
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'shipping' => $rawShipping,
];
$unit = $testee->fromPayPalResponse($response);
$this->assertNull($unit->payee());
}
public function testFromPayPalResponseShippingIsNull()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
$rawPayee = (object) ['payee' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('fromPayPalResponse')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('fromPayPalResponse')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('fromPayPalResponse')->with($rawItem)->andReturn($item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$response = (object) [
'reference_id' => 'default',
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
];
$unit = $testee->fromPayPalResponse($response);
$this->assertNull($unit->shipping());
}
public function testFromPayPalResponseNeedsReferenceId()
{
$amountFactory = Mockery::mock(AmountFactory::class);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
);
$response = (object) [
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => '',
'items' => [],
'payee' => '',
'shipping' => '',
];
$this->expectException(\Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException::class);
$testee->fromPayPalResponse($response);
}
public function testFromPayPalResponsePaymentsGetAppended()
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
$rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('fromPayPalResponse')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('fromPayPalResponse')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('fromPayPalResponse')->with($rawItem)->andReturn($item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shipping = Mockery::mock(Shipping::class);
$shippingFactory->expects('fromPayPalResponse')->with($rawShipping)->andReturn($shipping);
$paymentsFactory = Mockery::mock(PaymentsFactory::class);
$payments = Mockery::mock(Payments::class);
$paymentsFactory->expects('fromPayPalResponse')->with($rawPayments)->andReturn($payments);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
);
$response = (object)[
'reference_id' => 'default',
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
'payments' => $rawPayments,
];
$unit = $testee->fromPayPalResponse($response);
$this->assertEquals($payments, $unit->payments());
}
public function testFromPayPalResponsePaymentsIsNull()
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
$rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('fromPayPalResponse')->with($rawAmount)->andReturn($amount);
$payeeFactory = Mockery::mock(PayeeFactory::class);
$payee = Mockery::mock(Payee::class);
$payeeFactory->expects('fromPayPalResponse')->with($rawPayee)->andReturn($payee);
$payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('fromPayPalResponse')->with($rawItem)->andReturn($item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shipping = Mockery::mock(Shipping::class);
$shippingFactory->expects('fromPayPalResponse')->with($rawShipping)->andReturn($shipping);
$paymentsFactory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
$payeeRepository,
$payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
);
$response = (object)[
'reference_id' => 'default',
'description' => 'description',
'customId' => 'customId',
'invoiceId' => 'invoiceId',
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
'payee' => $rawPayee,
'shipping' => $rawShipping,
];
$unit = $testee->fromPayPalResponse($response);
$this->assertNull($unit->payments());
}
}

View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Config\Config;
use Inpsyde\PayPalCommerce\ApiClient\TestCase;
use Mockery;
class PayeeRepositoryTest extends TestCase
{
public function testDefault()
{
$merchantEmail = 'merchant_email';
$merchantId = 'merchant_id';
$testee = new PayeeRepository($merchantEmail, $merchantId);
$payee = $testee->payee();
$this->assertEquals($merchantId, $payee->merchantId());
$this->assertEquals($merchantEmail, $payee->email());
}
}

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\ApiClient;
use function Brain\Monkey\setUp;
use function Brain\Monkey\tearDown;
use function Brain\Monkey\Functions\expect;
use Mockery;
class TestCase extends \PHPUnit\Framework\TestCase
{
public function setUp(): void
{
parent::setUp();
expect('__')->andReturnUsing(function (string $text) {
return $text;
});
setUp();
}
public function tearDown(): void
{
tearDown();
Mockery::close();
parent::tearDown();
}
}