diff --git a/src/Adapter/ApplepayAdapter.php b/src/Adapter/ApplepayAdapter.php index cd33937a..72e496ba 100644 --- a/src/Adapter/ApplepayAdapter.php +++ b/src/Adapter/ApplepayAdapter.php @@ -36,10 +36,8 @@ class ApplepayAdapter private $request; /** - * @param string $merchantValidationURL URL for merchant validation request - * @param ApplepaySession $applePaySession Containing applepay session data. - * @param string $merchantValidationCertificatePath Path to merchant identification certificate - * @param string|null $merchantValidationCertificateKeyChainPath + * @param string $merchantValidationURL URL for merchant validation request + * @param ApplepaySession $applePaySession Containing applepay session data. * * @return string|null * @@ -47,20 +45,18 @@ class ApplepayAdapter */ public function validateApplePayMerchant( string $merchantValidationURL, - ApplepaySession $applePaySession, - string $merchantValidationCertificatePath, - ?string $merchantValidationCertificateKeyChainPath = null + ApplepaySession $applePaySession ): ?string { if (!$this->validMerchantValidationDomain($merchantValidationURL)) { - throw new ApplepayMerchantValidationException('Invalid URL used for merchantValidation request.'); + 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->init( - $merchantValidationURL, - $payload, - $merchantValidationCertificatePath, - $merchantValidationCertificateKeyChainPath - ); + $this->setOption(CURLOPT_URL, $merchantValidationURL); + $this->setOption(CURLOPT_POSTFIELDS, $payload); + $sessionResponse = $this->execute(); $this->close(); return $sessionResponse; @@ -71,6 +67,7 @@ public function validateApplePayMerchant( * * @param string $merchantValidationURL URL used for merchant validation request. * + * @return bool */ public function validMerchantValidationDomain(string $merchantValidationURL): bool { @@ -81,18 +78,20 @@ public function validMerchantValidationDomain(string $merchantValidationURL): bo } /** - * {@inheritDoc} + * @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($url, $payload, $sslCert, $caCert = null): void + public function init(string $sslCert, string $sslKey = null, string $caCert = null): void { $timeout = EnvironmentService::getTimeout(); $curlVerbose = EnvironmentService::isCurlVerbose(); - $this->request = curl_init($url); + $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_POSTFIELDS, $payload); $this->setOption(CURLOPT_FAILONERROR, false); $this->setOption(CURLOPT_TIMEOUT, $timeout); $this->setOption(CURLOPT_CONNECTTIMEOUT, $timeout); @@ -104,7 +103,10 @@ public function init($url, $payload, $sslCert, $caCert = null): void $this->setOption(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); $this->setOption(CURLOPT_SSLCERT, $sslCert); - if (isset($caCert)) { + if (isset($sslKey) && !empty($sslKey)) { + $this->setOption(CURLOPT_SSLKEY, $sslKey); + } + if (isset($caCert) && !empty($caCert)) { $this->setOption(CURLOPT_CAINFO, $caCert); } } diff --git a/src/Services/EnvironmentService.php b/src/Services/EnvironmentService.php index 4888a8d2..ac1ea74b 100755 --- a/src/Services/EnvironmentService.php +++ b/src/Services/EnvironmentService.php @@ -41,7 +41,7 @@ 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_CERTIFICATE = 'UNZER_APPLE_MERCHANT_CERTIFICATE_PATH'; + 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'; @@ -142,13 +142,13 @@ public static function getTestPublicKey($non3ds = false): string } /** - * Returns the apple merchant certificate path set via environment variable. + * Returns the path to apple merchant ID folder set via environment variable. * * @return string */ - public static function getAppleMerchantCertificatePath(): string + public static function getAppleMerchantIdPath(): string { - return stripslashes($_SERVER[self::ENV_VAR_TEST_APPLE_MERCHANT_CERTIFICATE] ?? ''); + return stripslashes($_SERVER[self::ENV_VAR_TEST_APPLE_MERCHANT_ID_FOLDER] ?? ''); } /** diff --git a/test/integration/ApplepayAdapterTest.php b/test/integration/ApplepayAdapterTest.php index 98f002b7..323f3bab 100644 --- a/test/integration/ApplepayAdapterTest.php +++ b/test/integration/ApplepayAdapterTest.php @@ -34,20 +34,16 @@ class ApplepayAdapterTest extends BaseIntegrationTest { + /** @var string $appleCaCertificatePath Path to ca Certificate file. */ + protected $appleCaCertificatePath; + /** @var string $merchantValidationUrl merchant validation url for testing. */ private $merchantValidationUrl; - private $merchantValidationCertificatePath; - private $appleCaCertificatePath; - - public function domainShouldBeValidatedCorrectlyDP() - { - 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], - ]; - } + /** @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 @@ -55,53 +51,126 @@ public function domainShouldBeValidatedCorrectlyDP() protected function setUp(): void { $this->merchantValidationUrl = 'https://apple-pay-gateway-cert.apple.com/paymentservices/startSession'; - $this->merchantValidationCertificatePath = EnvironmentService::getAppleMerchantCertificatePath(); + + $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 merchant validation request. * * @test * - * @throws \UnzerSDK\Exceptions\ApplepayMerchantValidationException + * @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->merchantValidationCertificatePath, - $this->appleCaCertificatePath + $applepaySession ); $this->assertNotNull($validationResponse); } /** - * test merchant validation request without ca certificate. + * @return ApplepaySession + */ + private function createApplepaySession(): ApplepaySession + { + return new ApplepaySession('merchantIdentifier', 'displayName', 'domainName'); + } + + /** + * Test merchant validation request without ca certificate. * * @test * - * @throws \UnzerSDK\Exceptions\ApplepayMerchantValidationException + * @throws ApplepayMerchantValidationException */ - public function merchantValidationWorksWithoutCaCert(): void + public function merchantValidationWorksWithApplepayCertOnly(): void { $applepaySession = $this->createApplepaySession(); $appleAdapter = new ApplepayAdapter(); + $appleAdapter->init($this->applepayCombinedCertPath); $validationResponse = $appleAdapter->validateApplePayMerchant( $this->merchantValidationUrl, - $applepaySession, - $this->merchantValidationCertificatePath + $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. * @@ -112,14 +181,14 @@ 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, - $this->merchantValidationCertificatePath + $applepaySession ); } @@ -141,11 +210,18 @@ public function domainShouldBeValidatedCorrectly($validationUrl, $expectedResult $this->assertEquals($expectedResult, $domainValidation); } - /** - * @return ApplepaySession + /** Provides different urls to test domain validation. + * + * @return array[] */ - private function createApplepaySession(): ApplepaySession + public function domainShouldBeValidatedCorrectlyDP(): array { - return new ApplepaySession('merchantIdentifier', 'displayName', 'domainName'); + 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], + ]; } }