+
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/']
];
}