diff --git a/CHANGELOG.md b/CHANGELOG.md index 409acb52..d306e412 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [1.1.1.1] +## [1.1.2.0](https://github.com/unzerdev/php-sdk/compare/1.1.1.1..1.1.2.0) +### Added +* Introduce the payment type Applepay. + +### Changed +* Examples: + * Card Examples - Ensure that error messages are displayed just one time. + * Configuration - Change default protocol to https. + * Configuration - Correct vendor name of path constant `UNZER_PAPI_FOLDER`. +* Update documentation links. + +## [1.1.1.1](https://github.com/unzerdev/php-sdk/compare/1.1.1.0..1.1.1.1) ### Fix * Change debug logging of failed tests that depend on another one to work as expected. @@ -19,13 +30,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * Card example and paypage examples use a dummy customer-email to ensure they work with 3ds2. * Several minor changes. -## [1.1.1.0] +## [1.1.1.0](https://github.com/unzerdev/php-sdk/compare/1.1.0.0..1.1.1.0) ### Changed * Add email property to payment type `card` to meet 3Ds2.x regulations. * Several minor changes. -## [1.1.0.0] +## [1.1.0.0](https://github.com/unzerdev/php-sdk/compare/1260b8314af1ac461e33f0cfb382ffcd0e87c105..1.1.0.0) ### Changed * Rebranding of the SDK. @@ -55,8 +66,4 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * API_ERROR_AUTHORIZE_ALREADY_CANCELLED * API_ERROR_CHARGE_ALREADY_CHARGED_BACK * API_ERROR_BASKET_ITEM_IMAGE_INVALID_EXTENSION - * ENV_VAR_NAME_DISABLE_TEST_LOGGING - -[1.1.0.0]: https://github.com/unzerdev/php-sdk/compare/1260b8314af1ac461e33f0cfb382ffcd0e87c105..1.1.0.0 -[1.1.1.0]: https://github.com/unzerdev/php-sdk/compare/1.1.0.0..1.1.1.0 -[1.1.1.1]: https://github.com/unzerdev/php-sdk/compare/1.1.1.0..1.1.1.1 + * ENV_VAR_NAME_DISABLE_TEST_LOGGING \ No newline at end of file diff --git a/README.md b/README.md index f79fa14a..a4e1a0ff 100755 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ This SDK provides for an easy way to connect to the Unzer Rest API. Please refer to the following documentation for installation instructions and usage information. -* [API Documentation](https://docs.unzer.com/docs/introduction) -* [PHP SDK Documentation](https://docs.unzer.com/docs/php-sdk) -* [How to use the examples](https://docs.unzer.com/docs/example-implementations) -* [Debugging](https://docs.unzer.com/docs/logging-and-debugging) +* [API Documentation](https://docs.unzer.com/overview) +* [PHP SDK Documentation](https://docs.unzer.com/integrate/php-sdk) +* [How to use the examples](https://docs.unzer.com/integrate/php-sdk/example-implementations) +* [Debugging](https://docs.unzer.com/integrate/php-sdk/logging-and-debugging) ## Supported payment types * Alipay diff --git a/examples/Bancontact/index.php b/examples/Bancontact/index.php index e5f4c6bc..adaef604 100644 --- a/examples/Bancontact/index.php +++ b/examples/Bancontact/index.php @@ -45,7 +45,7 @@ -

Click here to open our test data in new tab.

+

Click here to open our test data in new tab.

diff --git a/examples/BankTransfer/index.php b/examples/BankTransfer/index.php index 63f03aa7..fe927b51 100644 --- a/examples/BankTransfer/index.php +++ b/examples/BankTransfer/index.php @@ -45,7 +45,7 @@ -

Click here to open our test data in new tab.

+

Click here to open our test data in new tab.

diff --git a/examples/Card/index.php b/examples/Card/index.php index d5dd5532..644bf090 100644 --- a/examples/Card/index.php +++ b/examples/Card/index.php @@ -60,7 +60,7 @@
  • Secret: secret3
  • -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    @@ -151,7 +151,6 @@ $errorHolder.html('') } else { formFieldValid[e.type] = false; - $errorHolder.html(e.error) } payButton.disabled = !(formFieldValid.number && formFieldValid.expiry && formFieldValid.cvc); }; diff --git a/examples/CardExtended/index.php b/examples/CardExtended/index.php index 1acf4d15..c057c30e 100644 --- a/examples/CardExtended/index.php +++ b/examples/CardExtended/index.php @@ -60,7 +60,7 @@
  • Secret: secret3
  • -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    @@ -160,7 +160,6 @@ $errorHolder.html('') } else { formFieldValid[e.type] = false; - $errorHolder.html(e.error) } payButton.disabled = !( formFieldValid.number && diff --git a/examples/CardRecurring/index.php b/examples/CardRecurring/index.php index b69f0a20..3ddcce28 100644 --- a/examples/CardRecurring/index.php +++ b/examples/CardRecurring/index.php @@ -60,7 +60,7 @@
  • Secret: secret3
  • -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    @@ -124,7 +124,6 @@ $errorHolder.html('') } else { formFieldValid[e.type] = false; - $errorHolder.html(e.error) } payButton.disabled = !(formFieldValid.number && formFieldValid.expiry && formFieldValid.cvc && formFieldValid.email); }; diff --git a/examples/EmbeddedPayPage/index.php b/examples/EmbeddedPayPage/index.php index 77d8d26b..eec9b940 100644 --- a/examples/EmbeddedPayPage/index.php +++ b/examples/EmbeddedPayPage/index.php @@ -48,7 +48,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/IDeal/index.php b/examples/IDeal/index.php index 5788460b..ea306c78 100644 --- a/examples/IDeal/index.php +++ b/examples/IDeal/index.php @@ -45,7 +45,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/InstallmentSecured/index.php b/examples/InstallmentSecured/index.php index dadd4408..67e15bc9 100644 --- a/examples/InstallmentSecured/index.php +++ b/examples/InstallmentSecured/index.php @@ -45,7 +45,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/InvoiceSecured/index.php b/examples/InvoiceSecured/index.php index 12bc02b3..8fb40887 100644 --- a/examples/InvoiceSecured/index.php +++ b/examples/InvoiceSecured/index.php @@ -46,7 +46,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/PayPal/index.php b/examples/PayPal/index.php index f0f44b68..0432b529 100755 --- a/examples/PayPal/index.php +++ b/examples/PayPal/index.php @@ -51,7 +51,7 @@ Attention: We recommend to create your own PayPal test account here. -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/PayPalRecurring/index.php b/examples/PayPalRecurring/index.php index 2f6c0082..3ac46e2a 100644 --- a/examples/PayPalRecurring/index.php +++ b/examples/PayPalRecurring/index.php @@ -51,7 +51,7 @@ Attention: We recommend to create your own PayPal test account here. -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/Przelewy24/index.php b/examples/Przelewy24/index.php index dae5474d..c083145c 100644 --- a/examples/Przelewy24/index.php +++ b/examples/Przelewy24/index.php @@ -45,7 +45,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/SepaDirectDebitSecured/index.php b/examples/SepaDirectDebitSecured/index.php index fbc95b41..1e93311e 100644 --- a/examples/SepaDirectDebitSecured/index.php +++ b/examples/SepaDirectDebitSecured/index.php @@ -45,7 +45,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/Sofort/index.php b/examples/Sofort/index.php index 326f9859..ae49203f 100644 --- a/examples/Sofort/index.php +++ b/examples/Sofort/index.php @@ -45,7 +45,7 @@ -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/Wechatpay/index.php b/examples/Wechatpay/index.php index feec9920..0b5f688c 100755 --- a/examples/Wechatpay/index.php +++ b/examples/Wechatpay/index.php @@ -50,7 +50,7 @@
  • Password: 123
  • -

    Click here to open our test data in new tab.

    +

    Click here to open our test data in new tab.

    diff --git a/examples/_enableExamples.php b/examples/_enableExamples.php index 8abb33cb..748f594c 100755 --- a/examples/_enableExamples.php +++ b/examples/_enableExamples.php @@ -28,11 +28,12 @@ /* Set to true if you want to enable the examples */ define('UNZER_PAPI_EXAMPLES', false); -/* Please set this to your url. It must be reachable over the net*/ -define('UNZER_PAPI_URL', 'http://'.$_SERVER['HTTP_HOST']); +/* Please set this to your url. It must be reachable over the net +Webhooks will work with https only. However protocol can be changed to http if necessary. */ +define('UNZER_PAPI_URL', 'https://'.$_SERVER['HTTP_HOST']); /* Please enter the path from root directory to the example folder */ -define('UNZER_PAPI_FOLDER', '/vendor/unzer/php-sdk/examples/'); +define('UNZER_PAPI_FOLDER', '/vendor/unzerdev/php-sdk/examples/'); /* Please provide your own sandbox-keypair here. */ define('UNZER_PAPI_PRIVATE_KEY', 's-priv-***'); diff --git a/examples/index.php b/examples/index.php index 1564cdcf..decc85d9 100755 --- a/examples/index.php +++ b/examples/index.php @@ -310,7 +310,7 @@ function printMessage($type, $title, $text) server and redirected to a given RedirectUrl.
    -
    +
    Documentation
    @@ -327,7 +327,7 @@ function printMessage($type, $title, $text) The Payment Page will be shown as an Overlay in your own shop.
    -
    +
    Documentation
    diff --git a/src/Adapter/ApplepayAdapter.php b/src/Adapter/ApplepayAdapter.php new file mode 100644 index 00000000..72e496ba --- /dev/null +++ b/src/Adapter/ApplepayAdapter.php @@ -0,0 +1,165 @@ + + * + * @package UnzerSDK\Adapter + */ +namespace UnzerSDK\Adapter; + +use UnzerSDK\Constants\ApplepayValidationDomains; +use UnzerSDK\Exceptions\ApplepayMerchantValidationException; +use UnzerSDK\Resources\ExternalResources\ApplepaySession; +use UnzerSDK\Services\EnvironmentService; + +class ApplepayAdapter +{ + private $request; + + /** + * @param string $merchantValidationURL URL for merchant validation request + * @param ApplepaySession $applePaySession Containing applepay session data. + * + * @return string|null + * + * @throws ApplepayMerchantValidationException + */ + public function validateApplePayMerchant( + string $merchantValidationURL, + ApplepaySession $applePaySession + ): ?string { + if (!$this->validMerchantValidationDomain($merchantValidationURL)) { + throw new ApplepayMerchantValidationException("Invalid URL used for merchantValidation request."); + } + if ($this->request === null) { + throw new ApplepayMerchantValidationException('No curl adapter initiated yet. Make sure to cal init() function before.'); + } + $payload = $applePaySession->jsonSerialize(); + $this->setOption(CURLOPT_URL, $merchantValidationURL); + $this->setOption(CURLOPT_POSTFIELDS, $payload); + + $sessionResponse = $this->execute(); + $this->close(); + return $sessionResponse; + } + + /** + * Check whether domain of merchantValidationURL is allowed for validation request. + * + * @param string $merchantValidationURL URL used for merchant validation request. + * + * @return bool + */ + public function validMerchantValidationDomain(string $merchantValidationURL): bool + { + $domain = explode('/', $merchantValidationURL)[2] ?? ''; + + $UrlList = ApplepayValidationDomains::ALLOWED_VALIDATION_URLS; + return in_array($domain, $UrlList); + } + + /** + * @param string $sslCert Path to merchant identification certificate. + * @param string|null $sslKey Path to merchant identification key file. + * This is necessary if the ssl cert file doesn't contain key already. + * @param string|null $caCert Path to CA certificate. + */ + public function init(string $sslCert, string $sslKey = null, string $caCert = null): void + { + $timeout = EnvironmentService::getTimeout(); + $curlVerbose = EnvironmentService::isCurlVerbose(); + + $this->request = curl_init(); + $this->setOption(CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + $this->setOption(CURLOPT_POST, 1); + $this->setOption(CURLOPT_DNS_USE_GLOBAL_CACHE, false); + $this->setOption(CURLOPT_FAILONERROR, false); + $this->setOption(CURLOPT_TIMEOUT, $timeout); + $this->setOption(CURLOPT_CONNECTTIMEOUT, $timeout); + $this->setOption(CURLOPT_HTTP200ALIASES, (array)400); + $this->setOption(CURLOPT_RETURNTRANSFER, 1); + $this->setOption(CURLOPT_SSL_VERIFYPEER, 1); + $this->setOption(CURLOPT_SSL_VERIFYHOST, 2); + $this->setOption(CURLOPT_VERBOSE, $curlVerbose); + $this->setOption(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + + $this->setOption(CURLOPT_SSLCERT, $sslCert); + if (isset($sslKey) && !empty($sslKey)) { + $this->setOption(CURLOPT_SSLKEY, $sslKey); + } + if (isset($caCert) && !empty($caCert)) { + $this->setOption(CURLOPT_CAINFO, $caCert); + } + } + + /** + * Sets curl option. + * + * @param $name + * @param $value + */ + private function setOption($name, $value): void + { + curl_setopt($this->request, $name, $value); + } + + /** + * {@inheritDoc} + * + * @throws ApplepayMerchantValidationException + */ + public function execute(): ?string + { + $response = curl_exec($this->request); + $error = curl_error($this->request); + $errorNo = curl_errno($this->request); + + switch ($errorNo) { + case 0: + return $response; + break; + case CURLE_OPERATION_TIMEDOUT: + $errorMessage = 'Timeout: The Applepay API seems to be not available at the moment!'; + break; + default: + $errorMessage = $error . ' (curl_errno: ' . $errorNo . ').'; + break; + } + throw new ApplepayMerchantValidationException($errorMessage); + } + + /** + * @inheritDoc + */ + public function close(): void + { + curl_close($this->request); + } + + /** + * @inheritDoc + */ + public function getResponseCode(): string + { + return curl_getinfo($this->request, CURLINFO_HTTP_CODE); + } +} diff --git a/src/Constants/ApplepayValidationDomains.php b/src/Constants/ApplepayValidationDomains.php new file mode 100644 index 00000000..9f9824ab --- /dev/null +++ b/src/Constants/ApplepayValidationDomains.php @@ -0,0 +1,52 @@ + + * + * @package UnzerSDK\Constants + */ +namespace UnzerSDK\Constants; + +class ApplepayValidationDomains +{ + // URL list + public const ALLOWED_VALIDATION_URLS = [ + 'apple-pay-gateway.apple.com', + 'cn-apple-pay-gateway.apple.com', + 'apple-pay-gateway-nc-pod1.apple.com', + 'apple-pay-gateway-nc-pod2.apple.com', + 'apple-pay-gateway-nc-pod3.apple.com', + 'apple-pay-gateway-nc-pod4.apple.com', + 'apple-pay-gateway-nc-pod5.apple.com', + 'apple-pay-gateway-pr-pod1.apple.com', + 'apple-pay-gateway-pr-pod2.apple.com', + 'apple-pay-gateway-pr-pod3.apple.com', + 'apple-pay-gateway-pr-pod4.apple.com', + 'apple-pay-gateway-pr-pod5.apple.com', + 'cn-apple-pay-gateway-sh-pod1.apple.com', + 'cn-apple-pay-gateway-sh-pod2.apple.com', + 'cn-apple-pay-gateway-sh-pod3.apple.com', + 'cn-apple-pay-gateway-tj-pod1.apple.com', + 'cn-apple-pay-gateway-tj-pod2.apple.com', + 'cn-apple-pay-gateway-tj-pod3.apple.com', + 'apple-pay-gateway-cert.apple.com', + 'cn-apple-pay-gateway-cert.apple.com' + ]; +} diff --git a/src/Constants/IdStrings.php b/src/Constants/IdStrings.php index 13d4cd48..d80c3957 100755 --- a/src/Constants/IdStrings.php +++ b/src/Constants/IdStrings.php @@ -35,6 +35,7 @@ class IdStrings // Payment Types public const ALIPAY = 'ali'; + public const APPLEPAY = 'apl'; public const BANCONTACT = 'bct'; public const CARD = 'crd'; public const EPS = 'eps'; @@ -66,6 +67,7 @@ class IdStrings public const WEBHOOK = 'whk'; public const PAYMENT_TYPES = [ self::ALIPAY, + self::APPLEPAY, self::BANCONTACT, self::CARD, self::EPS, diff --git a/src/Exceptions/ApplepayMerchantValidationException.php b/src/Exceptions/ApplepayMerchantValidationException.php new file mode 100644 index 00000000..ba61e821 --- /dev/null +++ b/src/Exceptions/ApplepayMerchantValidationException.php @@ -0,0 +1,33 @@ + + * + * @package UnzerSDK + * + */ + +namespace UnzerSDK\Exceptions; + +use Exception; + +class ApplepayMerchantValidationException extends Exception +{ +} diff --git a/src/Interfaces/ResourceServiceInterface.php b/src/Interfaces/ResourceServiceInterface.php index 5fbf4798..551eab6c 100644 --- a/src/Interfaces/ResourceServiceInterface.php +++ b/src/Interfaces/ResourceServiceInterface.php @@ -171,7 +171,7 @@ public function updateBasket(Basket $basket): Basket; /** * Creates a PaymentType resource from the given PaymentType object. * This is used to create the payment object prior to any transaction. - * Usually this will be done by the unzerUI components (https://docs.unzer.com/docs/web-integration#step-3-create-your-payment-method) + * Usually this will be done by the unzerUI components (https://docs.unzer.com/integrate/web-integration/#step-3-create-your-payment-method) * * @param BasePaymentType $paymentType * diff --git a/src/Resources/EmbeddedResources/ApplePayHeader.php b/src/Resources/EmbeddedResources/ApplePayHeader.php new file mode 100644 index 00000000..094f12fe --- /dev/null +++ b/src/Resources/EmbeddedResources/ApplePayHeader.php @@ -0,0 +1,110 @@ + + * + * @package UnzerSDK\Resources\EmbeddedResources + */ +namespace UnzerSDK\Resources\EmbeddedResources; + +use UnzerSDK\Resources\AbstractUnzerResource; + +class ApplePayHeader extends AbstractUnzerResource +{ + /** @var string|null */ + protected $ephemeralPublicKey; + + /** @var string|null */ + protected $publicKeyHash; + + /** @var string|null */ + protected $transactionId; + + /** + * ApplePayHeader constructor. + * + * @param string|null $ephemeralPublicKey + * @param string|null $publicKeyHash + * @param string|null $transactionId + */ + public function __construct(?string $ephemeralPublicKey, ?string $publicKeyHash, ?string $transactionId = null) + { + $this->ephemeralPublicKey = $ephemeralPublicKey; + $this->publicKeyHash = $publicKeyHash; + $this->transactionId = $transactionId; + } + + /** + * @param string|null $ephemeralPublicKey + * + * @return ApplePayHeader + */ + public function setEphemeralPublicKey(?string $ephemeralPublicKey): ApplePayHeader + { + $this->ephemeralPublicKey = $ephemeralPublicKey; + return $this; + } + + /** + * @param string|null $publicKeyHash + * + * @return ApplePayHeader + */ + public function setPublicKeyHash(?string $publicKeyHash): ApplePayHeader + { + $this->publicKeyHash = $publicKeyHash; + return $this; + } + + /** + * @param string|null $transactionId + * + * @return ApplePayHeader + */ + public function setTransactionId(?string $transactionId): ApplePayHeader + { + $this->transactionId = $transactionId; + return $this; + } + + /** + * @return string|null + */ + public function getEphemeralPublicKey(): ?string + { + return $this->ephemeralPublicKey; + } + + /** + * @return string|null + */ + public function getPublicKeyHash(): ?string + { + return $this->publicKeyHash; + } + + /** + * @return string|null + */ + public function getTransactionId(): ?string + { + return $this->transactionId; + } +} diff --git a/src/Resources/ExternalResources/ApplepaySession.php b/src/Resources/ExternalResources/ApplepaySession.php new file mode 100644 index 00000000..8fb4a416 --- /dev/null +++ b/src/Resources/ExternalResources/ApplepaySession.php @@ -0,0 +1,133 @@ + + * + * @package UnzerSDK + * + */ + +namespace UnzerSDK\Resources\ExternalResources; + +class ApplepaySession +{ + /** + * This can be found in the Apple Developer Account + * + * @var string|null $merchantIdentifier + */ + private $merchantIdentifier; + + /** + * This is the Merchant-Name + * + * @var string|null $displayName + */ + private $displayName; + + /** + * This is the Domain Name which has been validated in the Apple Developer Account. + * + * @var string|null $domainName + */ + private $domainName; + + /** + * ApplepaySession constructor. + * + * @param string $merchantIdentifier + * @param string $displayName + * @param string $domainName + */ + public function __construct(string $merchantIdentifier, string $displayName, string $domainName) + { + $this->merchantIdentifier = $merchantIdentifier; + $this->displayName = $displayName; + $this->domainName = $domainName; + } + + /** + * Returns the json representation of this object's properties. + * + * @return false|string + */ + public function jsonSerialize() + { + $properties = get_object_vars($this); + return json_encode($properties, JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION); + } + + /** + * @return string|null + */ + public function getMerchantIdentifier(): ?string + { + return $this->merchantIdentifier; + } + + /** + * @param string|null $merchantIdentifier + * + * @return ApplepaySession + */ + public function setMerchantIdentifier(?string $merchantIdentifier): ApplepaySession + { + $this->merchantIdentifier = $merchantIdentifier; + return $this; + } + + /** + * @return string|null + */ + public function getDisplayName(): ?string + { + return $this->displayName; + } + + /** + * @param string|null $displayName + * + * @return ApplepaySession + */ + public function setDisplayName(?string $displayName): ApplepaySession + { + $this->displayName = $displayName; + return $this; + } + + /** + * @return string|null + */ + public function getDomainName(): ?string + { + return $this->domainName; + } + + /** + * @param string|null $domainName + * + * @return ApplepaySession + */ + public function setDomainName(?string $domainName): ApplepaySession + { + $this->domainName = $domainName; + return $this; + } +} diff --git a/src/Resources/PaymentTypes/Applepay.php b/src/Resources/PaymentTypes/Applepay.php new file mode 100644 index 00000000..2eb12631 --- /dev/null +++ b/src/Resources/PaymentTypes/Applepay.php @@ -0,0 +1,274 @@ + + * + * @package UnzerSDK\PaymentTypes + */ +namespace UnzerSDK\Resources\PaymentTypes; + +use stdClass; +use UnzerSDK\Adapter\HttpAdapterInterface; +use UnzerSDK\Resources\EmbeddedResources\ApplePayHeader; +use UnzerSDK\Traits\CanAuthorize; +use UnzerSDK\Traits\CanDirectCharge; +use UnzerSDK\Traits\HasGeoLocation; + +class Applepay extends BasePaymentType +{ + use CanDirectCharge; + use CanAuthorize; + use HasGeoLocation; + + /** @var string|null $applicationExpirationDate */ + private $applicationExpirationDate; + + /** @var string|null $applicationPrimaryAccountNumber */ + private $applicationPrimaryAccountNumber; + + /** @var string|null $currencyCode */ + private $currencyCode; + + /** @var string|null $data */ + protected $data; + + /** @var string|null $method */ + private $method; + + /** @var string|null $signature */ + protected $signature; + + /** @var float $transactionAmount */ + private $transactionAmount; + + /** @var string|null $version */ + protected $version; + + /** @var ApplePayHeader|null $header */ + protected $header; + + /** + * ApplePay constructor. + * + * @param string|null $version + * @param string|null $data + * @param string|null $signature + * @param ApplePayHeader|null $header + */ + public function __construct( + ?string $version, + ?string $data, + ?string $signature, + ?ApplePayHeader $header + ) { + $this->version = $version; + $this->data = $data; + $this->signature = $signature; + $this->header = $header; + } + + //applicationExpirationDate; + } + + /** + * @return string|null + */ + public function getApplicationPrimaryAccountNumber(): ?string + { + return $this->applicationPrimaryAccountNumber; + } + + /** + * @return string|null + */ + public function getCurrencyCode(): ?string + { + return $this->currencyCode; + } + + /** + * @return string|null + */ + public function getData(): ?string + { + return $this->data; + } + + /** + * @return ApplePayHeader|null + */ + public function getHeader(): ?ApplePayHeader + { + return $this->header; + } + + /** + * @return string|null + */ + public function getMethod(): ?string + { + return $this->method; + } + + /** + * @return string|null + */ + public function getSignature(): ?string + { + return $this->signature; + } + + /** + * @return float + */ + public function getTransactionAmount(): ?float + { + return $this->transactionAmount; + } + + /** + * @return string|null + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * @param string|null $applicationExpirationDate + * + * @return $this + */ + protected function setApplicationExpirationDate(?string $applicationExpirationDate): Applepay + { + $this->applicationExpirationDate = $applicationExpirationDate; + return $this; + } + + /** + * @param string|null $applicationPrimaryAccountNumber + * + * @return $this + */ + protected function setApplicationPrimaryAccountNumber(?string $applicationPrimaryAccountNumber): Applepay + { + $this->applicationPrimaryAccountNumber = $applicationPrimaryAccountNumber; + return $this; + } + + /** + * @param string|null $currencyCode + * + * @return $this + */ + protected function setCurrencyCode(?string $currencyCode): Applepay + { + $this->currencyCode = $currencyCode; + return $this; + } + + /** + * @param string|null $data + * + * @return $this + */ + public function setData(?string $data): Applepay + { + $this->data = $data; + return $this; + } + + /** + * @param ApplePayHeader $header + * + * @return Applepay + */ + public function setHeader(ApplePayHeader $header): Applepay + { + $this->header = $header; + return $this; + } + + /** + * @param string|null $method + * + * @return $this + */ + protected function setMethod(?string $method): Applepay + { + $this->method = $method; + return $this; + } + + /** + * @param string|null $signature + * + * @return $this + */ + public function setSignature(?string $signature): Applepay + { + $this->signature = $signature; + return $this; + } + + /** + * @param float $transactionAmount + * + * @return $this + */ + protected function setTransactionAmount(float $transactionAmount): Applepay + { + $this->transactionAmount = $transactionAmount; + return $this; + } + + /** + * @param string|null $version + * + * @return $this + */ + public function setVersion(?string $version): Applepay + { + $this->version = $version; + return $this; + } + + // + + /** + * @inheritDoc + */ + public function handleResponse(stdClass $response, $method = HttpAdapterInterface::REQUEST_GET): void + { + parent::handleResponse($response, $method); + + if (isset($response->header)) { + $this->header = new ApplePayHeader(null, null, null); + $this->header->handleResponse($response->header); + } + } +} diff --git a/src/Services/EnvironmentService.php b/src/Services/EnvironmentService.php index fbfdbaa9..ac1ea74b 100755 --- a/src/Services/EnvironmentService.php +++ b/src/Services/EnvironmentService.php @@ -41,6 +41,9 @@ class EnvironmentService public const ENV_VAR_TEST_PRIVATE_KEY_NON_3DS = 'UNZER_PAPI_TEST_PRIVATE_KEY_NON_3DS'; public const ENV_VAR_TEST_PUBLIC_KEY_NON_3DS = 'UNZER_PAPI_TEST_PUBLIC_KEY_NON_3DS'; + public const ENV_VAR_TEST_APPLE_MERCHANT_ID_FOLDER = 'UNZER_APPLE_MERCHANT_ID_PATH'; + public const ENV_VAR_TEST_APPLE_CA_CERTIFICATE = 'UNZER_APPLE_CA_CERTIFICATE_PATH'; + private const ENV_VAR_NAME_TIMEOUT = 'UNZER_PAPI_TIMEOUT'; private const DEFAULT_TIMEOUT = 60; @@ -137,4 +140,24 @@ public static function getTestPublicKey($non3ds = false): string $key = stripslashes($_SERVER[$variableName] ?? ''); return empty($key) ? '' : $key; } + + /** + * Returns the path to apple merchant ID folder set via environment variable. + * + * @return string + */ + public static function getAppleMerchantIdPath(): string + { + return stripslashes($_SERVER[self::ENV_VAR_TEST_APPLE_MERCHANT_ID_FOLDER] ?? ''); + } + + /** + * Returns the CA certificate path set via environment variable. + * + * @return string + */ + public static function getAppleCaCertificatePath(): string + { + return stripslashes($_SERVER[self::ENV_VAR_TEST_APPLE_CA_CERTIFICATE] ?? ''); + } } diff --git a/src/Services/ResourceService.php b/src/Services/ResourceService.php index 8d9faa01..ae571585 100755 --- a/src/Services/ResourceService.php +++ b/src/Services/ResourceService.php @@ -30,6 +30,7 @@ use UnzerSDK\Constants\ApiResponseCodes; use UnzerSDK\Constants\IdStrings; use UnzerSDK\Exceptions\UnzerApiException; +use UnzerSDK\Resources\PaymentTypes\Applepay; use UnzerSDK\Unzer; use UnzerSDK\Interfaces\ResourceServiceInterface; use UnzerSDK\Resources\AbstractUnzerResource; @@ -515,6 +516,9 @@ public function fetchPaymentType($typeId): BasePaymentType case IdStrings::ALIPAY: $paymentType = new Alipay(); break; + case IdStrings::APPLEPAY: + $paymentType = new Applepay(null, null, null, null); + break; case IdStrings::BANCONTACT: $paymentType = new Bancontact(); break; diff --git a/src/Unzer.php b/src/Unzer.php index 65faadf8..bd9a1be6 100644 --- a/src/Unzer.php +++ b/src/Unzer.php @@ -63,7 +63,7 @@ class Unzer implements UnzerParentInterface, PaymentServiceInterface, ResourceSe public const BASE_URL = 'api.unzer.com'; public const API_VERSION = 'v1'; public const SDK_TYPE = 'UnzerPHP'; - public const SDK_VERSION = '1.1.1.1'; + public const SDK_VERSION = '1.1.2.0'; /** @var string $key */ private $key; @@ -98,7 +98,7 @@ class Unzer implements UnzerParentInterface, PaymentServiceInterface, ResourceSe * @param string $key The private key your received from your Unzer contact person. * @param string $locale The locale of the customer defining defining the translation (e.g. 'en-GB' or 'de-DE'). * - * @link https://docs.unzer.com/docs/web-integration#section-localization-and-languages + * @link https://docs.unzer.com/integrate/web-integration/#section-localization-and-languages * * @throws RuntimeException A RuntimeException will be thrown if the key is not of type private. */ diff --git a/test/integration/ApplepayAdapterTest.php b/test/integration/ApplepayAdapterTest.php new file mode 100644 index 00000000..323f3bab --- /dev/null +++ b/test/integration/ApplepayAdapterTest.php @@ -0,0 +1,227 @@ + + * + * @package UnzerSDK + * + */ + +namespace UnzerSDK\test\integration; + +use UnzerSDK\Adapter\ApplepayAdapter; +use UnzerSDK\Exceptions\ApplepayMerchantValidationException; +use UnzerSDK\Resources\ExternalResources\ApplepaySession; +use UnzerSDK\Services\EnvironmentService; +use UnzerSDK\test\BaseIntegrationTest; + +class ApplepayAdapterTest extends BaseIntegrationTest +{ + /** @var string $appleCaCertificatePath Path to ca Certificate file. */ + protected $appleCaCertificatePath; + /** @var string $merchantValidationUrl merchant validation url for testing. */ + private $merchantValidationUrl; + /** @var string $applepayCombinedCertPath Path to combined certificate file. */ + private $applepayCombinedCertPath; + /** @var string $applepayCertPath Path to merchant ID certificate file. */ + private $applepayCertPath; + /** @var string $applepayKeyPath Path to merchant ID key file. */ + private $applepayKeyPath; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->merchantValidationUrl = 'https://apple-pay-gateway-cert.apple.com/paymentservices/startSession'; + + $appleMerchantIdPath = EnvironmentService::getAppleMerchantIdPath(); + $this->applepayCertPath = $appleMerchantIdPath . 'merchant_id.pem'; + $this->applepayKeyPath = $appleMerchantIdPath . 'merchant_id.key'; + $this->applepayCombinedCertPath = $appleMerchantIdPath . 'apple-pay-cert.pem'; + $this->appleCaCertificatePath = EnvironmentService::getAppleCaCertificatePath(); + } + + /** + * Test merchant validation request. + * + * @test + * + * @throws ApplepayMerchantValidationException + */ + public function verifyMerchantValidationRequest(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCertPath, $this->applepayKeyPath, $this->appleCaCertificatePath); + + $validationResponse = $appleAdapter->validateApplePayMerchant( + $this->merchantValidationUrl, + $applepaySession + ); + + $this->assertNotNull($validationResponse); + } + + /** + * @return ApplepaySession + */ + private function createApplepaySession(): ApplepaySession + { + return new ApplepaySession('merchantIdentifier', 'displayName', 'domainName'); + } + + /** + * Test merchant validation request without ca certificate. + * + * @test + * + * @throws ApplepayMerchantValidationException + */ + public function merchantValidationWorksWithApplepayCertOnly(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCombinedCertPath); + + $validationResponse = $appleAdapter->validateApplePayMerchant( + $this->merchantValidationUrl, + $applepaySession + ); + + $this->assertNotNull($validationResponse); + } + + /** + * Test merchant validation request without ca certificate. + * + * @test + * + * @throws ApplepayMerchantValidationException + */ + public function merchantValidationWorksWithCertAndKey(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCertPath, $this->applepayKeyPath); + + $validationResponse = $appleAdapter->validateApplePayMerchant( + $this->merchantValidationUrl, + $applepaySession + ); + + $this->assertNotNull($validationResponse); + } + + /** + * Test merchant validation request without key and only the merchant id certificate should throw exception. + * + * @test + * + * @throws ApplepayMerchantValidationException + */ + public function missingKeyShouldThrowException(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCertPath); + + $this->expectException(ApplepayMerchantValidationException::class); + $appleAdapter->validateApplePayMerchant( + $this->merchantValidationUrl, + $applepaySession + ); + } + + /** + * Test merchant validation request without init() call should throw exception. + * + * @test + * + * @throws ApplepayMerchantValidationException + */ + public function missingInitCallThrowsException(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + + $this->expectException(ApplepayMerchantValidationException::class); + $this->expectExceptionMessage('No curl adapter initiated yet. Make sure to cal init() function before.'); + + $appleAdapter->validateApplePayMerchant( + $this->merchantValidationUrl, + $applepaySession + ); + } + + /** + * Merchant validation call should throw Exception if domain of Validation url is invalid. + * + * @test + * + */ + public function merchantValidationThrowsErrorForInvalidDomain(): void + { + $applepaySession = $this->createApplepaySession(); + $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCombinedCertPath); + + $this->expectException(ApplepayMerchantValidationException::class); + $this->expectExceptionMessage('Invalid URL used for merchantValidation request.'); + + $appleAdapter->validateApplePayMerchant( + 'https://invalid.domain.com/some/path', + $applepaySession + ); + } + + /** + * test merchant validation request without ca certificate. + * + * @dataProvider domainShouldBeValidatedCorrectlyDP + * @test + * + * @param mixed $validationUrl + * @param mixed $expectedResult + * + */ + public function domainShouldBeValidatedCorrectly($validationUrl, $expectedResult): void + { + $appleAdapter = new ApplepayAdapter(); + + $domainValidation = $appleAdapter->validMerchantValidationDomain($validationUrl); + $this->assertEquals($expectedResult, $domainValidation); + } + + /** Provides different urls to test domain validation. + * + * @return array[] + */ + public function domainShouldBeValidatedCorrectlyDP(): array + { + return [ + 'invalid: example.domain.com' => ['https://example.domain.com', false], + 'valid: https://apple-pay-gateway.apple.com/some/path' => ['https://apple-pay-gateway.apple.com/some/path', true], + 'valid: https://cn-apple-pay-gateway.apple.com' => ['https://cn-apple-pay-gateway.apple.com', true], + 'invalid: apple-pay-gateway-nc-pod1.apple.com' => ['apple-pay-gateway-nc-pod1.apple.com', false], + 'invalid: (empty)' => ['', false], + ]; + } +} diff --git a/test/integration/PaymentTypes/ApplepayTest.php b/test/integration/PaymentTypes/ApplepayTest.php new file mode 100644 index 00000000..aaf3d53a --- /dev/null +++ b/test/integration/PaymentTypes/ApplepayTest.php @@ -0,0 +1,424 @@ + + * + * @package UnzerSDK + * + */ + +namespace UnzerSDK\test\integration\PaymentTypes; + +use UnzerSDK\Constants\ApiResponseCodes; +use UnzerSDK\Exceptions\UnzerApiException; +use UnzerSDK\Resources\PaymentTypes\Applepay; +use UnzerSDK\test\BaseIntegrationTest; + +class ApplepayTest extends BaseIntegrationTest +{ + /** + * Verify applepay can be created and fetched. + * + * @test + * + * @throws UnzerApiException + */ + public function applepayShouldBeCreatableAndFetchable(): void + { + $applepay = $this->createApplepayObject(); + $this->unzer->createPaymentType($applepay); + $this->assertNotNull($applepay->getId()); + + /** @var Applepay $fetchedPaymentTyp */ + $fetchedPaymentTyp = $this->unzer->fetchPaymentType($applepay->getId()); + $this->assertInstanceOf(Applepay::class, $fetchedPaymentTyp); + $this->assertNull($fetchedPaymentTyp->getVersion()); + $this->assertNull($fetchedPaymentTyp->getData()); + $this->assertNull($fetchedPaymentTyp->getSignature()); + $this->assertNull($fetchedPaymentTyp->getHeader()); + } + + /** + * Verify that applepay is chargeable + * + * @test + * + * @throws UnzerApiException + */ + public function applepayShouldBeChargeable(): void + { + $applepay = $this->createApplepayObject(); + $this->unzer->createPaymentType($applepay); + $charge = $applepay->charge(100.0, 'EUR', self::RETURN_URL); + $this->assertNotNull($charge->getId()); + $this->assertNull($charge->getRedirectUrl()); + } + + /** + * Verify that applepay is chargeable + * + * @test + * + * @throws UnzerApiException + */ + public function applepayCanBeAuthorized(): void + { + $applepay = $this->createApplepayObject(); + $this->unzer->createPaymentType($applepay); + $authorization = $applepay->authorize(1.0, 'EUR', self::RETURN_URL); + + // verify authorization has been created + $this->assertNotNull($authorization->getId()); + $this->assertNull($authorization->getRedirectUrl()); + + // verify payment object has been created + $payment = $authorization->getPayment(); + $this->assertNotNull($payment); + $this->assertNotNull($payment->getId()); + + // verify resources are linked properly + $this->assertSame($authorization, $payment->getAuthorization()); + $this->assertSame($applepay, $payment->getPaymentType()); + + // verify the payment object has been updated properly + $this->assertAmounts($payment, 1.0, 0.0, 1.0, 0.0); + $this->assertTrue($payment->isPending()); + } + + /** + * Verify the applepay can perform charges and creates a payment object doing so. + * + * @test + * + * @throws UnzerApiException + */ + public function applepayCanPerformChargeAndCreatesPaymentObject(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + + $charge = $applepay->charge(1.0, 'EUR', self::RETURN_URL, null, null, null, null, false); + + // verify charge has been created + $this->assertNotNull($charge->getId()); + + // verify payment object has been created + $payment = $charge->getPayment(); + $this->assertNotNull($payment); + $this->assertNotNull($payment->getId()); + + // verify resources are linked properly + $this->assertEquals($charge->expose(), $payment->getCharge($charge->getId())->expose()); + $this->assertSame($applepay, $payment->getPaymentType()); + + // verify the payment object has been updated properly + $this->assertAmounts($payment, 0.0, 1.0, 1.0, 0.0); + $this->assertTrue($payment->isCompleted()); + } + + /** + * Verify the applepay can charge the full amount of the authorization and the payment state is updated accordingly. + * + * @test + * + * @throws UnzerApiException + */ + public function fullChargeAfterAuthorize(): void + { + $applepay = $this->createApplepayObject(); + $this->unzer->createPaymentType($applepay); + + $authorization = $applepay->authorize(1.0, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $authorization->getPayment(); + + // pre-check to verify changes due to fullCharge call + $this->assertAmounts($payment, 1.0, 0.0, 1.0, 0.0); + $this->assertTrue($payment->isPending()); + + $charge = $this->unzer->chargeAuthorization($payment->getId()); + $paymentNew = $charge->getPayment(); + + // verify payment has been updated properly + $this->assertAmounts($paymentNew, 0.0, 1.0, 1.0, 0.0); + $this->assertTrue($paymentNew->isCompleted()); + } + + /** + * Verify the applepay can charge part of the authorized amount and the payment state is updated accordingly. + * + * @test + * + * @throws UnzerApiException + */ + public function partialChargeAfterAuthorization(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + $authorization = $this->unzer->authorize( + 100.0, + 'EUR', + $applepay, + self::RETURN_URL, + null, + null, + null, + null, + false + ); + + $payment = $authorization->getPayment(); + $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0); + $this->assertTrue($payment->isPending()); + + $charge = $this->unzer->chargeAuthorization($payment->getId(), 20); + $payment1 = $charge->getPayment(); + $this->assertAmounts($payment1, 80.0, 20.0, 100.0, 0.0); + $this->assertTrue($payment1->isPartlyPaid()); + + $charge = $this->unzer->chargeAuthorization($payment->getId(), 20); + $payment2 = $charge->getPayment(); + $this->assertAmounts($payment2, 60.0, 40.0, 100.0, 0.0); + $this->assertTrue($payment2->isPartlyPaid()); + + $charge = $this->unzer->chargeAuthorization($payment->getId(), 60); + $payment3 = $charge->getPayment(); + $this->assertAmounts($payment3, 00.0, 100.0, 100.0, 0.0); + $this->assertTrue($payment3->isCompleted()); + } + + /** + * Verify that an exception is thrown when trying to charge more than authorized. + * + * @test + * + * @throws UnzerApiException + */ + public function exceptionShouldBeThrownWhenChargingMoreThenAuthorized(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + $authorization = $applepay->authorize(100.0000, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $authorization->getPayment(); + $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0); + $this->assertTrue($payment->isPending()); + + $charge = $this->unzer->chargeAuthorization($payment->getId(), 50); + $payment1 = $charge->getPayment(); + $this->assertAmounts($payment1, 50.0, 50.0, 100.0, 0.0); + $this->assertTrue($payment1->isPartlyPaid()); + + $this->expectException(UnzerApiException::class); + $this->expectExceptionCode(ApiResponseCodes::API_ERROR_CHARGED_AMOUNT_HIGHER_THAN_EXPECTED); + $this->unzer->chargeAuthorization($payment->getId(), 70); + } + + /** + * Verify applepay authorize can be canceled. + * + * @test + * + * @throws UnzerApiException + */ + public function applepayAuthorizeCanBeCanceled(): void + { + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($this->createApplepayObject()); + $authorize = $applepay->authorize(100.0, 'EUR', self::RETURN_URL, null, null, null, null, false); + + $cancel = $authorize->cancel(); + $this->assertNotNull($cancel); + $this->assertNotEmpty($cancel->getId()); + } + + /** + * Verify the applepay payment can be charged until it is fully charged and the payment is updated accordingly. + * + * @test + * + * @throws UnzerApiException + */ + public function partialAndFullChargeAfterAuthorization(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + $authorization = $applepay->authorize(100.0000, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $authorization->getPayment(); + + $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0); + $this->assertTrue($payment->isPending()); + + $charge = $this->unzer->chargeAuthorization($payment->getId(), 20); + $payment1 = $charge->getPayment(); + $this->assertAmounts($payment1, 80.0, 20.0, 100.0, 0.0); + $this->assertTrue($payment1->isPartlyPaid()); + + $charge = $this->unzer->chargeAuthorization($payment->getId()); + $payment2 = $charge->getPayment(); + $this->assertAmounts($payment2, 0.0, 100.0, 100.0, 0.0); + $this->assertTrue($payment2->isCompleted()); + } + + /** + * Authorization can be fetched. + * + * @test + * + * @throws UnzerApiException + */ + public function authorizationShouldBeFetchable(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + $authorization = $applepay->authorize(100.0000, 'EUR', self::RETURN_URL); + $payment = $authorization->getPayment(); + + $fetchedAuthorization = $this->unzer->fetchAuthorization($payment->getId()); + $this->assertEquals($fetchedAuthorization->getId(), $authorization->getId()); + } + + /** + * @test + * + * @throws UnzerApiException + */ + public function fullCancelAfterCharge(): void + { + $applepay = $this->createApplepayObject(); + $this->unzer->createPaymentType($applepay); + $charge = $applepay->charge(100.0, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $charge->getPayment(); + + $this->assertAmounts($payment, 0.0, 100.0, 100.0, 0.0); + $this->assertTrue($payment->isCompleted()); + + $payment->cancelAmount(); + $this->assertAmounts($payment, 0.0, 0.0, 100.0, 100.0); + $this->assertTrue($payment->isCanceled()); + } + + /** + * Verify a applepay payment can be cancelled after being fully charged. + * + * @test + * + * @throws UnzerApiException + */ + public function fullCancelOnFullyChargedPayment(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + + $authorization = $applepay->authorize(100.0000, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $authorization->getPayment(); + + $this->assertAmounts($payment, 100.0, 0.0, 100.0, 0.0); + $this->assertTrue($payment->isPending()); + + $payment->charge(10.0); + $this->assertAmounts($payment, 90.0, 10.0, 100.0, 0.0); + $this->assertTrue($payment->isPartlyPaid()); + + $payment->charge(90.0); + $this->assertAmounts($payment, 0.0, 100.0, 100.0, 0.0); + $this->assertTrue($payment->isCompleted()); + + $cancellation = $payment->cancelAmount(); + $this->assertNotEmpty($cancellation); + $this->assertAmounts($payment, 0.0, 0.0, 100.0, 100.0); + $this->assertTrue($payment->isCanceled()); + } + + /** + * Full cancel on partly charged auth canceled charges. + * + * @test + * + * @throws UnzerApiException + */ + public function fullCancelOnPartlyPaidAuthWithCanceledCharges(): void + { + $applepay = $this->createApplepayObject(); + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($applepay); + + $authorization = $applepay->authorize(100.0000, 'EUR', self::RETURN_URL, null, null, null, null, false); + $payment = $authorization->getPayment(); + + $payment->charge(10.0); + $this->assertAmounts($payment, 90.0, 10.0, 100.0, 0.0); + + $charge = $payment->charge(10.0); + $this->assertAmounts($payment, 80.0, 20.0, 100.0, 0.0); + $this->assertTrue($payment->isPartlyPaid()); + + $charge->cancel(); + $this->assertAmounts($payment, 80.0, 10.0, 100.0, 10.0); + $this->assertTrue($payment->isPartlyPaid()); + + $payment->cancelAmount(); + $this->assertTrue($payment->isCanceled()); + } + + /** + * Verify applepay charge can be canceled. + * + * @test + * + * @throws UnzerApiException + */ + public function applepayChargeCanBeCanceled(): void + { + /** @var Applepay $applepay */ + $applepay = $this->unzer->createPaymentType($this->createApplepayObject()); + $charge = $applepay->charge(100.0, 'EUR', self::RETURN_URL, null, null, null, null, false); + + $cancel = $charge->cancel(); + $this->assertNotNull($cancel); + $this->assertNotEmpty($cancel->getId()); + } + + /** + * @return Applepay + */ + private function createApplepayObject(): Applepay + { + $applepayAutorization = '{ + "version": "EC_v1", + "data": "TwQBBorcg6aEb5eidSJm5fNG5sih+R+xgeJbvAX8oMQ7EXhIWOE+ACnvBFHOkZOjI+ump/zVrBXTMRYSw32WMWXPuiRDlYu8DMNuV3qKrbC+G5Du5qfxsm8BxJCXkc/DqtGqc70o8TJCn9lM5ePQjS3io4HDonkN4b4L20GfyEVW1QyvozaMa1u7/gaS6OhhXNk65Z70+xCZlOGmgDtgcdZK+TQIYgRLzyP+1+mpqd61pQ3vJELB8ngMoleCGd1DHx2kVWsudZQ5q97sUjpZV2ySfPXLMhWHYYfvcvSx3dKDAywUoR8clUeDKtoZ4LsBO/B8XM/T4JKnFmWfr7Z25E88vfMWIs8JpxIC5OKAPZfVZoDSNs+4LR+twVxlD5B2xkvG6ln6j4cQ+CFmiq9FPSDgQJsn8O7K9Ag0odXiK6mZczOWt2HCHaw0thF/WpudObVlmw5NN1r54/Jxoichp+DJ2Hl1NJqDHKS1fNyXQcR5jqID7QOcpQi0gE332bOTIz/xe+u328GMCl6Rms3JJxFnnskfEA7nicIH8DLFeSbG8jloLyKBBLk=", + "signature": "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", + "header": { + "ephemeralPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW7hYAxjCeE/r9SSRX/hJsfO+VxLvUqIzyeGn6lZ1v/pYYS66Bz0dsSzoMMZg8G32TAPXUr97AD4zCXfcQoZaOA==", + "publicKeyHash": "zqO5Y3ldWWm4NnIkfGCvJILw30rp3y46Jsf21gE8CNg=", + "transactionId": "94f6b37149ae2098efb287ed0ade704284cff3f672ef7f0dc17614b31e926b9d" + } + }'; + + $applepay = new Applepay(null, null, null, null); + $applepay->handleResponse(json_decode($applepayAutorization)); + return $applepay; + } +} diff --git a/test/unit/Adapter/ApplepaySessionTest.php b/test/unit/Adapter/ApplepaySessionTest.php new file mode 100644 index 00000000..84dfa01a --- /dev/null +++ b/test/unit/Adapter/ApplepaySessionTest.php @@ -0,0 +1,42 @@ + + * + * @package UnzerSDK + * + */ + +namespace UnzerSDK\test\unit\Adapter; + +use PHPUnit\Framework\TestCase; +use UnzerSDK\Resources\ExternalResources\ApplepaySession; + +class ApplepaySessionTest extends TestCase +{ + public function testJsonSerialize(): void + { + $applepaySession = new ApplepaySession('merchantIdentifier', 'displayName', 'domainName'); + $expectedJson = '{"merchantIdentifier": "merchantIdentifier", "displayName": "displayName", "domainName": "domainName"}'; + + $jsonSerialize = $applepaySession->jsonSerialize(); + $this->assertJsonStringEqualsJsonString($expectedJson, $jsonSerialize); + } +} diff --git a/test/unit/Resources/AbstractUnzerResourceTest.php b/test/unit/Resources/AbstractUnzerResourceTest.php index 494a680b..571cc0a6 100644 --- a/test/unit/Resources/AbstractUnzerResourceTest.php +++ b/test/unit/Resources/AbstractUnzerResourceTest.php @@ -33,6 +33,7 @@ use UnzerSDK\Constants\Salutations; use UnzerSDK\Constants\TransactionTypes; use UnzerSDK\Resources\InstalmentPlans; +use UnzerSDK\Resources\PaymentTypes\Applepay; use UnzerSDK\Unzer; use UnzerSDK\Resources\AbstractUnzerResource; use UnzerSDK\Resources\Basket; @@ -443,6 +444,7 @@ public function uriDataProvider(): array 'Ideal' => [new Ideal(), 'parent/resource/path/types/ideal'], 'EPS' => [new EPS(), 'parent/resource/path/types/eps'], 'Alipay' => [new Alipay(), 'parent/resource/path/types/alipay'], + 'ApplePay' => [new Applepay('EC_v1', 'data', 'sig', null), 'parent/resource/path/types/applepay'], 'SepaDirectDebit' => [new SepaDirectDebit(''), 'parent/resource/path/types/sepa-direct-debit'], 'SepaDirectDebitSecured' => [new SepaDirectDebitSecured(''), 'parent/resource/path/types/sepa-direct-debit-secured'], 'Invoice' => [new Invoice(), 'parent/resource/path/types/invoice'], @@ -467,14 +469,15 @@ public function fetchUriDataProvider() { return [ // Payment types. + 'Alipay' => [new Alipay(), 'parent/resource/path/types'], + 'ApplePay' => [new Applepay('EC_v1', 'data', 'sig', null), 'parent/resource/path/types'], 'Card' => [new Card('', '03/30'), 'parent/resource/path/types'], - 'Ideal' => [new Ideal(), 'parent/resource/path/types'], 'EPS' => [new EPS(), 'parent/resource/path/types'], - 'Alipay' => [new Alipay(), 'parent/resource/path/types'], + 'Ideal' => [new Ideal(), 'parent/resource/path/types'], + 'InstallmentSecured' => [new InstallmentSecured(), 'parent/resource/path/types'], + 'Invoice' => [new Invoice(), 'parent/resource/path/types'], 'SepaDirectDebit' => [new SepaDirectDebit(''), 'parent/resource/path/types'], 'SepaDirectDebitSecured' => [new SepaDirectDebitSecured(''), 'parent/resource/path/types'], - 'Invoice' => [new Invoice(), 'parent/resource/path/types'], - 'InstallmentSecured' => [new InstallmentSecured(), 'parent/resource/path/types'], // Other resources Uris should behave as before. 'Customer' => [new Customer(), 'parent/resource/path/customers'], diff --git a/test/unit/Resources/EmbeddedResources/ApplePayHeaderTest.php b/test/unit/Resources/EmbeddedResources/ApplePayHeaderTest.php new file mode 100644 index 00000000..0dc6a203 --- /dev/null +++ b/test/unit/Resources/EmbeddedResources/ApplePayHeaderTest.php @@ -0,0 +1,47 @@ + + * + * @package UnzerSDK\test\unit + */ +namespace UnzerSDK\test\unit\Resources\EmbeddedResources; + +use UnzerSDK\Resources\EmbeddedResources\ApplePayHeader; +use PHPUnit\Framework\TestCase; + +class ApplePayHeaderTest extends TestCase +{ + /** + * Verify the resource data is set properly. + * + * @test + */ + public function constructorShouldSetParameters(): void + { + $applepayHeader = new ApplePayHeader('ephemeralPublicKey', 'publicKeyHash', 'transactionId'); + + $this->assertEquals('ephemeralPublicKey', $applepayHeader->getEphemeralPublicKey()); + $this->assertEquals('publicKeyHash', $applepayHeader->getPublicKeyHash()); + $this->assertEquals('transactionId', $applepayHeader->getTransactionId()); + } +} diff --git a/test/unit/Resources/PaymentTypes/ApplePayTest.php b/test/unit/Resources/PaymentTypes/ApplePayTest.php new file mode 100644 index 00000000..0b6374df --- /dev/null +++ b/test/unit/Resources/PaymentTypes/ApplePayTest.php @@ -0,0 +1,149 @@ + + * + * @package UnzerSDK\test\unit + */ +namespace UnzerSDK\test\unit\Resources\PaymentTypes; + +use UnzerSDK\Resources\EmbeddedResources\ApplePayHeader; +use UnzerSDK\Resources\PaymentTypes\Applepay; +use UnzerSDK\test\BasePaymentTest; + +class ApplePayTest extends BasePaymentTest +{ + /** + * Verify the resource data is set properly. + * + * @test + */ + public function constructorShouldSetParameters(): void + { + $version = 'EC_v1'; + $data = 'some-Data'; + $signature = 'mySignature'; + $applepay = new Applepay($version, $data, $signature, $this->getTestApplePayHeader()); + + $this->assertEquals($version, $applepay->getVersion()); + $this->assertEquals($data, $applepay->getData()); + $this->assertEquals($signature, $applepay->getSignature()); + $this->assertInstanceOf(ApplePayHeader::class, $applepay->getHeader()); + } + + /** + * Test Apple Pay json serialization. + * + * @test + */ + public function jsonSerializationExposesOnlyRequestParameter(): void + { + $applepay = $this->getTestApplepay(); + + $expectedJson = '{ "data": "data", "header": { "ephemeralPublicKey": "ephemeralPublicKey", "publicKeyHash": ' . + '"publicKeyHash", "transactionId": "transactionId" }, "signature": "sig", "version": "EC_v1" }'; + + $this->assertJsonStringEqualsJsonString($expectedJson, $applepay->jsonSerialize()); + } + + /** + * Test Apple Pay json response handling. + * + * @test + */ + public function responseShouldBeMappedCorrectly(): void + { + $applepay = new Applepay(null, null, null, null); + + $jsonResponse = '{ + "id": "s-apl-faucbirhd6yy", + "method": "apple-pay", + "recurring": false, + "geoLocation": { + "clientIp": "115.77.189.143", + "countryCode": "" + }, + "applicationPrimaryAccountNumber": "370295******922", + "applicationExpirationDate": "07/2020", + "currencyCode": "EUR", + "transactionAmount": "1.5000" + }'; + + $applepay->handleResponse(json_decode($jsonResponse)); + + $this->assertEquals('s-apl-faucbirhd6yy', $applepay->getId()); + $this->assertEquals('apple-pay', $applepay->getMethod()); + $this->assertEquals('370295******922', $applepay->getApplicationPrimaryAccountNumber()); + $this->assertEquals('07/2020', $applepay->getApplicationExpirationDate()); + $this->assertEquals('EUR', $applepay->getCurrencyCode()); + $this->assertSame(1.5000, $applepay->getTransactionAmount()); + $this->assertNotNull($applepay->getGeoLocation()); + } + + /** + * Test Apple Pay json response handling. + * + * @test + */ + public function applepayAuthorizationShouldBeMappedCorrectly(): void + { + $applepay = new Applepay(null, null, null, null); + + $jsonResponse = '{ + "version": "EC_v1", + "data": "data", + "signature": "signature", + "header": { + "ephemeralPublicKey": "ephemeralPublicKey", + "publicKeyHash": "publicKeyHash", + "transactionId": "transactionId" + } + }'; + + $applepay->handleResponse(json_decode($jsonResponse)); + + $this->assertEquals('EC_v1', $applepay->getVersion()); + $this->assertEquals('data', $applepay->getData()); + $this->assertEquals('signature', $applepay->getSignature()); + $applePayHeader = $applepay->getHeader(); + $this->assertNotNull($applePayHeader); + $this->assertEquals('ephemeralPublicKey', $applePayHeader->getEphemeralPublicKey()); + $this->assertEquals('publicKeyHash', $applePayHeader->getPublicKeyHash()); + $this->assertEquals('transactionId', $applePayHeader->getTransactionId()); + } + + /** + * @return ApplePayHeader + */ + private function getTestApplePayHeader(): ApplePayHeader + { + return new ApplePayHeader('ephemeralPublicKey', 'publicKeyHash', 'transactionId'); + } + + /** + * @return Applepay + */ + private function getTestApplepay(): Applepay + { + return new Applepay('EC_v1', 'data', 'sig', $this->getTestApplePayHeader()); + } +} diff --git a/test/unit/Services/ResourceServiceTest.php b/test/unit/Services/ResourceServiceTest.php index 5489518a..e6336f2f 100755 --- a/test/unit/Services/ResourceServiceTest.php +++ b/test/unit/Services/ResourceServiceTest.php @@ -1143,26 +1143,27 @@ public function fetchResourceByUrlShouldFetchTheDesiredResourceDP(): array public function fetchResourceByUrlForAPaymentTypeShouldCallFetchPaymentTypeDP(): array { return [ + 'ALIPAY' => ['s-ali-xen2ybcovn56', 'https://api.unzer.com/v1/types/alipay/s-ali-xen2ybcovn56/'], + 'APPLEPAY' => ['s-apl-xen2ybcovn56', 'https://api.unzer.com/v1/types/appelpay/s-apl-xen2ybcovn56/'], + 'BANCONTACT' => ['s-bct-xen2ybcovn56', 'https://api.unzer.com/v1/types/bancontact/s-bct-xen2ybcovn56/'], 'CARD' => ['s-crd-xen2ybcovn56', 'https://api.unzer.com/v1/types/card/s-crd-xen2ybcovn56/'], + 'EPS' => ['s-eps-xen2ybcovn56', 'https://api.unzer.com/v1/types/eps/s-eps-xen2ybcovn56/'], 'GIROPAY' => ['s-gro-xen2ybcovn56', 'https://api.unzer.com/v1/types/giropay/s-gro-xen2ybcovn56/'], + 'HIRE_PURCHASE_DIRECT_DEBIT' => ['s-hdd-xen2ybcovn56', 'https://api.unzer.com/v1/types/hire-purchase-direct-debit/s-hdd-xen2ybcovn56/'], 'IDEAL' => ['s-idl-xen2ybcovn56', 'https://api.unzer.com/v1/types/ideal/s-idl-xen2ybcovn56/'], 'INVOICE' => ['s-ivc-xen2ybcovn56', 'https://api.unzer.com/v1/types/invoice/s-ivc-xen2ybcovn56/'], + 'INVOICE_FACTORING' => ['s-ivf-xen2ybcovn56', 'https://api.unzer.com/v1/types/wechatpay/s-ivf-xen2ybcovn56/'], 'INVOICE_GUARANTEED' => ['s-ivg-xen2ybcovn56', 'https://api.unzer.com/v1/types/invoice-guaranteed/s-ivg-xen2ybcovn56/'], 'INVOICE_SECURED' => ['s-ivs-xen2ybcovn56', 'https://api.unzer.com/v1/types/invoice-secured/s-ivs-xen2ybcovn56/'], - 'INVOICE_FACTORING' => ['s-ivf-xen2ybcovn56', 'https://api.unzer.com/v1/types/wechatpay/s-ivf-xen2ybcovn56/'], + 'Installment_SECURED' => ['s-ins-xen2ybcovn56', 'https://api.unzer.com/v1/types/installment-secured/s-ins-xen2ybcovn56/'], 'PAYPAL' => ['s-ppl-xen2ybcovn56', 'https://api.unzer.com/v1/types/paypal/s-ppl-xen2ybcovn56/'], + 'PIS' => ['s-pis-xen2ybcovn56', 'https://api.unzer.com/v1/types/pis/s-pis-xen2ybcovn56/'], 'PREPAYMENT' => ['s-ppy-xen2ybcovn56', 'https://api.unzer.com/v1/types/prepayment/s-ppy-xen2ybcovn56/'], 'PRZELEWY24' => ['s-p24-xen2ybcovn56', 'https://api.unzer.com/v1/types/przelewy24/s-p24-xen2ybcovn56/'], - 'SEPA_DIRECT_DEBIT_GUARANTEED' => ['s-ddg-xen2ybcovn56', 'https://api.unzer.com/v1/types/direct-debit-guaranteed/s-ddg-xen2ybcovn56/'], 'SEPA_DIRECT_DEBIT' => ['s-sdd-xen2ybcovn56', 'https://api.unzer.com/v1/types/direct-debit/s-sdd-xen2ybcovn56/'], + 'SEPA_DIRECT_DEBIT_GUARANTEED' => ['s-ddg-xen2ybcovn56', 'https://api.unzer.com/v1/types/direct-debit-guaranteed/s-ddg-xen2ybcovn56/'], 'SOFORT' => ['s-sft-xen2ybcovn56', 'https://api.unzer.com/v1/types/sofort/s-sft-xen2ybcovn56/'], - 'PIS' => ['s-pis-xen2ybcovn56', 'https://api.unzer.com/v1/types/pis/s-pis-xen2ybcovn56/'], - 'EPS' => ['s-eps-xen2ybcovn56', 'https://api.unzer.com/v1/types/eps/s-eps-xen2ybcovn56/'], - 'ALIPAY' => ['s-ali-xen2ybcovn56', 'https://api.unzer.com/v1/types/alipay/s-ali-xen2ybcovn56/'], - 'WECHATPAY' => ['s-wcp-xen2ybcovn56', 'https://api.unzer.com/v1/types/wechatpay/s-wcp-xen2ybcovn56/'], - 'HIRE_PURCHASE_DIRECT_DEBIT' => ['s-hdd-xen2ybcovn56', 'https://api.unzer.com/v1/types/hire-purchase-direct-debit/s-hdd-xen2ybcovn56/'], - 'Installment_SECURED' => ['s-ins-xen2ybcovn56', 'https://api.unzer.com/v1/types/installment-secured/s-ins-xen2ybcovn56/'], - 'BANCONTACT' => ['s-bct-xen2ybcovn56', 'https://api.unzer.com/v1/types/bancontact/s-bct-xen2ybcovn56/'] + 'WECHATPAY' => ['s-wcp-xen2ybcovn56', 'https://api.unzer.com/v1/types/wechatpay/s-wcp-xen2ybcovn56/'] ]; }