diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6797a1..409acb52 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,43 +3,60 @@ 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.0][1.1.1.0] +## [1.1.1.1] + +### Fix +* Change debug logging of failed tests that depend on another one to work as expected. +* PayPal recurring example: Response handling changed to check the recurring status of the payment type. + +### Added +* Extended testing for Instalment payment type. +* Cards (extended) example using email UI element. + +### Changed +* Remove PhpUnit 8 support. +* Card recurring example using email UI element. +* Card example and paypage examples use a dummy customer-email to ensure they work with 3ds2. +* Several minor changes. + +## [1.1.1.0] ### Changed -* Add email property to payment type `card` to meet 3Ds2.x regulations. -* Several minor changes. +* Add email property to payment type `card` to meet 3Ds2.x regulations. +* Several minor changes. -## [1.1.0.0][1.1.0.0] +## [1.1.0.0] ### Changed -* Rebranding of the SDK. -* Removed payment type string from URL when fetching a payment type resource. -* Replace payment methods guaranteed/factoring by secured payment methods, i.e.: - * `InvoiceGuaranteed` and `InvoiceFactoring` replaced by `InvoiceSecured` - * `SepaDirectDebitGuaranteed` replaced by `SepaDirectDebitSecured` - * `HirePurchaseDirectDebit` replaced by `InstallmentSecured` - * Basket is now mandatory for all those payment types above. -* Added mapping of old payment type ids to the new payment type resources. -* Constant in `\UnzerSDK\Constants\ApiResponseCodes` got renamed: - * `API_ERROR_IVF_REQUIRES_CUSTOMER` renamed to `API_ERROR_FACTORING_REQUIRES_CUSTOMER`. - * `API_ERROR_IVF_REQUIRES_BASKET` renamed to `API_ERROR_FACTORING_REQUIRES_BASKET`. -* Several minor changes. +* Rebranding of the SDK. +* Removed payment type string from URL when fetching a payment type resource. +* Replace payment methods guaranteed/factoring by secured payment methods, i.e.: + * `InvoiceGuaranteed` and `InvoiceFactoring` replaced by `InvoiceSecured` + * `SepaDirectDebitGuaranteed` replaced by `SepaDirectDebitSecured` + * `HirePurchaseDirectDebit` replaced by `InstallmentSecured` + * Basket is now mandatory for all those payment types above. +* Added mapping of old payment type ids to the new payment type resources. +* Constant in `\UnzerSDK\Constants\ApiResponseCodes` got renamed: + * `API_ERROR_IVF_REQUIRES_CUSTOMER` renamed to `API_ERROR_FACTORING_REQUIRES_CUSTOMER`. + * `API_ERROR_IVF_REQUIRES_BASKET` renamed to `API_ERROR_FACTORING_REQUIRES_BASKET`. +* Several minor changes. ### Remove -* Remove deprecated methods: - * getAmountTotal - * setAmountTotal - * getCardHolder - * setHolder - * cancel - * cancelAllCharges - * cancelAuthorization - * getResource - * fetchResource -* Remove deprecated constants: - * API_ERROR_AUTHORIZE_ALREADY_CANCELLED - * API_ERROR_CHARGE_ALREADY_CHARGED_BACK - * API_ERROR_BASKET_ITEM_IMAGE_INVALID_EXTENSION - * ENV_VAR_NAME_DISABLE_TEST_LOGGING +* Remove deprecated methods: + * getAmountTotal + * setAmountTotal + * getCardHolder + * setHolder + * cancel + * cancelAllCharges + * cancelAuthorization + * getResource + * fetchResource +* Remove deprecated constants: + * 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 diff --git a/composer.json b/composer.json index c9927508..56c11db6 100755 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": ">6.5 <9.0", + "phpunit/phpunit": ">6.5 <8.0", "friendsofphp/php-cs-fixer": "^2.0" }, "suggest": { diff --git a/examples/Card/Controller.php b/examples/Card/Controller.php index 28d599a7..60f79664 100644 --- a/examples/Card/Controller.php +++ b/examples/Card/Controller.php @@ -70,6 +70,8 @@ function redirect($url, $merchantMessage = '', $clientMessage = '') // The 3D secured flag can be used to switch between 3ds and non-3ds. // If your merchant is only configured for one of those you can omit the flag. $customer = CustomerFactory::createCustomer('Max', 'Mustermann'); + $customer->setEmail('test@test.com'); + switch ($transactionType) { case 'charge': $transaction = $unzer->charge(12.99, 'EUR', $paymentTypeId, RETURN_CONTROLLER_URL, $customer, null, null, null, $use3Ds); diff --git a/examples/CardExtended/Constants.php b/examples/CardExtended/Constants.php new file mode 100644 index 00000000..fc0ac781 --- /dev/null +++ b/examples/CardExtended/Constants.php @@ -0,0 +1,29 @@ + + * + * @package UnzerSDK\examples + */ + +require_once __DIR__ . '/../Constants.php'; + +define('EXAMPLE_URL', EXAMPLE_BASE_FOLDER . 'CardExtended'); +define('CONTROLLER_URL', EXAMPLE_URL . '/Controller.php'); diff --git a/examples/CardExtended/Controller.php b/examples/CardExtended/Controller.php new file mode 100644 index 00000000..ecc18524 --- /dev/null +++ b/examples/CardExtended/Controller.php @@ -0,0 +1,112 @@ + + * + * @package UnzerSDK\examples + */ + +/** Require the constants of this example */ +require_once __DIR__ . '/Constants.php'; + +/** @noinspection PhpIncludeInspection */ +/** Require the composer autoloader file */ +require_once __DIR__ . '/../../../../autoload.php'; + +use UnzerSDK\examples\ExampleDebugHandler; +use UnzerSDK\Exceptions\UnzerApiException; +use UnzerSDK\Unzer; +use UnzerSDK\Resources\CustomerFactory; + +session_start(); +session_unset(); + +$clientMessage = 'Something went wrong. Please try again later.'; +$merchantMessage = 'Something went wrong. Please try again later.'; + +function redirect($url, $merchantMessage = '', $clientMessage = '') +{ + $_SESSION['merchantMessage'] = $merchantMessage; + $_SESSION['clientMessage'] = $clientMessage; + header('Location: ' . $url); + die(); +} + +// You will need the id of the payment type created in the frontend (index.php) +if (!isset($_POST['resourceId'])) { + redirect(FAILURE_URL, 'Resource id is missing!', $clientMessage); +} +$paymentTypeId = $_POST['resourceId']; + +// These lines are just for this example +$transactionType = $_POST['transaction_type'] ?? 'authorize'; + +// Catch API errors, write the message to your log and show the ClientMessage to the client. +try { + // Create an Unzer object using your private key and register a debug handler if you want to. + $unzer = new Unzer(UNZER_PAPI_PRIVATE_KEY); + $unzer->setDebugMode(true)->setDebugHandler(new ExampleDebugHandler()); + + // Create a charge/authorize transaction + // For 3Ds2 compliance an email need to be set either in card type or in customer resource. + // If your merchant is only configured for one of those you can omit the flag. + $customer = CustomerFactory::createCustomer('Max', 'Mustermann'); + switch ($transactionType) { + case 'charge': + $transaction = $unzer->charge(12.99, 'EUR', $paymentTypeId, RETURN_CONTROLLER_URL, $customer, null, null, null, true); + break; + case 'payout': + $transaction = $unzer->payout(12.99, 'EUR', $paymentTypeId, RETURN_CONTROLLER_URL, $customer); + break; + default: + $transaction = $unzer->authorize(12.99, 'EUR', $paymentTypeId, RETURN_CONTROLLER_URL, $customer, null, null, null, true); + break; + } + + // You'll need to remember the paymentId for later in the ReturnController (in case of 3ds) + $_SESSION['PaymentId'] = $transaction->getPaymentId(); + $_SESSION['ShortId'] = $transaction->getShortId(); + + // Redirect to the 3ds page or to success depending on the state of the transaction + $payment = $transaction->getPayment(); + $redirect = !empty($transaction->getRedirectUrl()); + + switch (true) { + case (!$redirect && $transaction->isSuccess()): + redirect(SUCCESS_URL); + break; + case (!$redirect && $transaction->isPending()): + redirect(PENDING_URL); + break; + case ($redirect && $transaction->isPending()): + redirect($transaction->getRedirectUrl()); + break; + } + + // Check the result message of the transaction to find out what went wrong. + $merchantMessage = $transaction->getMessage()->getCustomer(); +} catch (UnzerApiException $e) { + $merchantMessage = $e->getMerchantMessage(); + $clientMessage = $e->getClientMessage(); +} catch (RuntimeException $e) { + $merchantMessage = $e->getMessage(); +} +redirect(FAILURE_URL, $merchantMessage, $clientMessage); diff --git a/examples/CardExtended/index.php b/examples/CardExtended/index.php new file mode 100644 index 00000000..1acf4d15 --- /dev/null +++ b/examples/CardExtended/index.php @@ -0,0 +1,200 @@ + + * + * @package UnzerSDK\examples + */ + +/** Require the constants of this example */ +require_once __DIR__ . '/Constants.php'; + +/** @noinspection PhpIncludeInspection */ +/** Require the composer autoloader file */ +require_once __DIR__ . '/../../../../autoload.php'; +?> + + + + + + Unzer UI Examples + + + + + + + +

Example Data for VISA:

+ + +

Example Data for Mastercard:

+ + +

Click here to open our test data in new tab.

+ +
+ +
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + + +
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+ + + + diff --git a/examples/CardRecurring/index.php b/examples/CardRecurring/index.php index 7c81f33f..b69f0a20 100644 --- a/examples/CardRecurring/index.php +++ b/examples/CardRecurring/index.php @@ -80,6 +80,9 @@ +
+ +
@@ -102,6 +105,10 @@ containerId: 'card-element-id-cvc', onlyIframe: false }); + Card.create('email', { + containerId: 'card-element-id-email', + onlyIframe: false + }); // General event handling let formFieldValid = {}; @@ -119,7 +126,7 @@ formFieldValid[e.type] = false; $errorHolder.html(e.error) } - payButton.disabled = !(formFieldValid.number && formFieldValid.expiry && formFieldValid.cvc); + payButton.disabled = !(formFieldValid.number && formFieldValid.expiry && formFieldValid.cvc && formFieldValid.email); }; Card.addEventListener('change', eventHandlerCardInput); diff --git a/examples/EmbeddedPayPage/Controller.php b/examples/EmbeddedPayPage/Controller.php index d1ba78ef..937b71ec 100644 --- a/examples/EmbeddedPayPage/Controller.php +++ b/examples/EmbeddedPayPage/Controller.php @@ -59,6 +59,7 @@ // Create a charge/authorize transaction $customer = CustomerFactory::createCustomer('Max', 'Mustermann'); + $customer->setEmail('test@test.com'); // These are the mandatory parameters for the payment page ... $paypage = new Paypage(119.00, 'EUR', RETURN_CONTROLLER_URL); diff --git a/examples/HostedPayPage/Controller.php b/examples/HostedPayPage/Controller.php index f8a275a7..f14ba82c 100644 --- a/examples/HostedPayPage/Controller.php +++ b/examples/HostedPayPage/Controller.php @@ -64,6 +64,7 @@ function redirect($url, $merchantMessage = '', $clientMessage = '') // Create a charge/authorize transaction $customer = CustomerFactory::createCustomer('Max', 'Mustermann'); + $customer->setEmail('test@test.com'); // These are the mandatory parameters for the payment page ... $paypage = new Paypage(119.0, 'EUR', RETURN_CONTROLLER_URL); diff --git a/examples/InstallmentSecured/PlaceOrderController.php b/examples/InstallmentSecured/PlaceOrderController.php index 615f7a23..998f359a 100644 --- a/examples/InstallmentSecured/PlaceOrderController.php +++ b/examples/InstallmentSecured/PlaceOrderController.php @@ -72,7 +72,7 @@ function redirect($url, $merchantMessage = '', $clientMessage = '') } // Check the result message of the transaction to find out what went wrong. - $merchantMessage = $authorize->getMessage()->getCustomer(); + $merchantMessage = $charge->getMessage()->getCustomer(); } catch (UnzerApiException $e) { $merchantMessage = $e->getMerchantMessage(); $clientMessage = $e->getClientMessage(); diff --git a/examples/PayPalRecurring/Constants.php b/examples/PayPalRecurring/Constants.php index d0fd6277..68f70111 100644 --- a/examples/PayPalRecurring/Constants.php +++ b/examples/PayPalRecurring/Constants.php @@ -27,3 +27,4 @@ define('EXAMPLE_URL', EXAMPLE_BASE_FOLDER . 'PayPalRecurring'); define('CONTROLLER_URL', EXAMPLE_URL . '/Controller.php'); +define('MY_RETURN_CONTROLLER_URL', EXAMPLE_URL . '/ReturnController.php'); diff --git a/examples/PayPalRecurring/Controller.php b/examples/PayPalRecurring/Controller.php index 8fb8535d..990b2e60 100644 --- a/examples/PayPalRecurring/Controller.php +++ b/examples/PayPalRecurring/Controller.php @@ -61,7 +61,7 @@ function redirect($url, $merchantMessage = '', $clientMessage = '') $unzer = new Unzer(UNZER_PAPI_PRIVATE_KEY); $unzer->setDebugMode(true)->setDebugHandler(new ExampleDebugHandler()); - $recurring = $unzer->activateRecurringPayment($paymentTypeId, RETURN_CONTROLLER_URL); + $recurring = $unzer->activateRecurringPayment($paymentTypeId, MY_RETURN_CONTROLLER_URL); // You'll need to remember the paymentId for later in the ReturnController (in case of 3ds) $_SESSION['PaymentTypeId'] = $paymentTypeId; diff --git a/examples/PayPalRecurring/ReturnController.php b/examples/PayPalRecurring/ReturnController.php new file mode 100644 index 00000000..13beb304 --- /dev/null +++ b/examples/PayPalRecurring/ReturnController.php @@ -0,0 +1,79 @@ + + * + * @package UnzerSDK\examples + */ + +/** Require the constants of this example */ +require_once __DIR__ . '/Constants.php'; + +/** @noinspection PhpIncludeInspection */ +/** Require the composer autoloader file */ +require_once __DIR__ . '/../../../../autoload.php'; + +use UnzerSDK\examples\ExampleDebugHandler; +use UnzerSDK\Exceptions\UnzerApiException; +use UnzerSDK\Unzer; +use UnzerSDK\Resources\PaymentTypes\Card; + +session_start(); + +$clientMessage = 'Something went wrong. Please try again later.'; +$merchantMessage = 'Something went wrong. Please try again later.'; + +function redirect($url, $merchantMessage = '', $clientMessage = '') +{ + $_SESSION['merchantMessage'] = $merchantMessage; + $_SESSION['clientMessage'] = $clientMessage; + header('Location: ' . $url); + die(); +} + +// Retrieve the paymentId you remembered within the Controller +if (!isset($_SESSION['PaymentTypeId'])) { + redirect(FAILURE_URL, 'The payment type id is missing.', $clientMessage); +} +$paymentTypeId = $_SESSION['PaymentTypeId']; + +// Catch API errors, write the message to your log and show the ClientMessage to the client. +try { + // Create an Unzer object using your private key and register a debug handler if you want to. + $unzer = new Unzer(UNZER_PAPI_PRIVATE_KEY); + $unzer->setDebugMode(true)->setDebugHandler(new ExampleDebugHandler()); + + // Redirect to success if the payment has been successfully completed or is still in handled. + /** @var Card $paymentType */ + $paymentType = $unzer->fetchPaymentType($paymentTypeId); + + if ($paymentType->isRecurring()) { + redirect(SUCCESS_URL); + } + +} catch (UnzerApiException $e) { + $merchantMessage = $e->getMerchantMessage(); + $clientMessage = $e->getClientMessage(); +} catch (RuntimeException $e) { + $merchantMessage = $e->getMessage(); +} + +redirect(FAILURE_URL, $merchantMessage, $clientMessage); diff --git a/examples/index.php b/examples/index.php index 7dc8360f..1564cdcf 100755 --- a/examples/index.php +++ b/examples/index.php @@ -88,12 +88,26 @@ function printMessage($type, $title, $text)
Card
You can try authorize, charge and payout transactions with or without 3Ds. + This example submits email via customer resource.
Try
+
+
+
Card (extended)
+
+ Including email and holder fields. + Adding more information to the card can improve risk acceptance. + This example submits email via payment type resource. +
+
+
+ Try +
+
Card Recurring
diff --git a/phpunit.xml b/phpunit.xml index d0d58a86..58ad4061 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,6 +1,6 @@ unzer->getDebugHandler(); + $debugHandler = self::getDebugHandler(); if ($this->getStatus() === BaseTestRunner::STATUS_PASSED) { $debugHandler->clearTempLog(); diff --git a/test/BasePaymentTest.php b/test/BasePaymentTest.php index 91a28b90..69407b6a 100755 --- a/test/BasePaymentTest.php +++ b/test/BasePaymentTest.php @@ -52,6 +52,19 @@ class BasePaymentTest extends TestCase /** @var Unzer $unzer */ protected $unzer; + protected static $debughandler; + + /** + * @return TestDebugHandler + */ + public static function getDebugHandler(): TestDebugHandler + { + if (!self::$debughandler instanceof TestDebugHandler) { + self::$debughandler = new TestDebugHandler(); + } + return self::$debughandler; + } + /** * Creates and returns an Unzer object if it does not exist yet. * Uses an invalid but well formed default key if no key is given. @@ -66,7 +79,7 @@ protected function getUnzerObject($privateKey = 's-priv-1234'): Unzer { if (!$this->unzer instanceof Unzer) { $this->unzer = (new Unzer($privateKey)) - ->setDebugHandler(new TestDebugHandler()) + ->setDebugHandler(self::getDebugHandler()) ->setDebugMode(true); } return $this->unzer; diff --git a/test/integration/PaymentTypes/InstallmentSecuredTest.php b/test/integration/PaymentTypes/InstallmentSecuredTest.php index 72152aa3..4eb5f5c0 100644 --- a/test/integration/PaymentTypes/InstallmentSecuredTest.php +++ b/test/integration/PaymentTypes/InstallmentSecuredTest.php @@ -29,6 +29,7 @@ use UnzerSDK\Constants\ApiResponseCodes; use UnzerSDK\Exceptions\UnzerApiException; +use UnzerSDK\Resources\AbstractUnzerResource; use UnzerSDK\Resources\Customer; use UnzerSDK\Resources\CustomerFactory; use UnzerSDK\Resources\EmbeddedResources\Address; @@ -81,7 +82,7 @@ public function instalmentPlanShouldBeSelectable(): void * * @test */ - public function hddTypeShouldBeFechable(): InstallmentSecured + public function hddTypeShouldBeFetchable(): InstallmentSecured { // Mock a hdd Type $date = $this->getTodaysDateString(); @@ -130,9 +131,11 @@ public function hddTypeShouldBeFechable(): InstallmentSecured * Verify fetched hdd type can be authorized and charged * * @test - * @depends hddTypeShouldBeFechable + * @depends hddTypeShouldBeFetchable * - * @param InvoiceSecured $hddType fetched ins type. + * @param InstallmentSecured $hddType fetched ins type. + * + * @return AbstractUnzerResource|Charge * * @throws UnzerApiException */ @@ -140,7 +143,7 @@ public function hddTypeAuthorizeAndCharge(InstallmentSecured $hddType) { $customer = $this->getMaximumCustomer(); $basket = $this->createBasket(); - /** @var Authorization $auth */ + $auth = $hddType->authorize(119.00, 'EUR', 'https://unzer.com', $customer, null, null, $basket); $charge = $auth->getPayment()->charge(); $this->assertNotNull($auth); @@ -320,6 +323,67 @@ public function verifyPartlyCancelChargedInstallmentSecured(): void $this->assertTrue($payment->isCompleted()); } + /** + * Verify full cancel of charged HP after shipment. + * + * @test + * + * @depends verifyChargingAnInitializedInstallmentSecured + */ + public function verifyChargeAndFullCancelAnInitializedInstallmentSecuredAfterShipment(): void + { + $yesterday = $this->getYesterdaysTimestamp(); + $plans = $this->unzer->fetchInstallmentPlans(119.0, 'EUR', 4.99, $yesterday); + $this->assertGreaterThan(0, count($plans->getPlans())); + + /** @var InstalmentPlan $selectedPlan */ + $selectedPlan = $plans->getPlans()[0]; + $ins = new InstallmentSecured($selectedPlan, 'DE89370400440532013000', 'Manuel Weißmann', $yesterday, 'COBADEFFXXX', $this->getTodaysDateString(), $this->getTomorrowsTimestamp()); + $this->unzer->createPaymentType($ins); + + $authorize = $ins->authorize(119.0, 'EUR', self::RETURN_URL, $this->getCustomer(), null, null, $basket = $this->createBasket()); + $payment = $authorize->getPayment(); + + $hddCharge = $payment->charge(); + $invoiceId = 'i' . self::generateRandomId(); + $ship = $this->unzer->ship($hddCharge->getPayment(), $invoiceId); + $this->assertNotNull($ship); + + $cancel = $payment->cancelAmount(); + $this->assertGreaterThan(0, count($cancel)); + } + + /** + * Verify full cancel of charged HP after shipment. + * + * @test + * + * @depends verifyChargingAnInitializedInstallmentSecured + */ + public function verifyPartlyCancelChargedInstallmentSecuredAfterShipment(): void + { + $yesterday = $this->getYesterdaysTimestamp(); + $plans = $this->unzer->fetchInstallmentPlans(119.0, 'EUR', 4.99, $yesterday); + $this->assertGreaterThan(0, count($plans->getPlans())); + + /** @var InstalmentPlan $selectedPlan */ + $selectedPlan = $plans->getPlans()[0]; + $ins = new InstallmentSecured($selectedPlan, 'DE89370400440532013000', 'Manuel Weißmann', $yesterday, 'COBADEFFXXX', $this->getTodaysDateString(), $this->getTomorrowsTimestamp()); + $this->unzer->createPaymentType($ins); + + $authorize = $ins->authorize(119.0, 'EUR', self::RETURN_URL, $this->getCustomer(), null, null, $basket = $this->createBasket()); + $payment = $authorize->getPayment(); + + $hddCharge = $payment->charge(); + $invoiceId = 'i' . self::generateRandomId(); + $ship = $this->unzer->ship($hddCharge->getPayment(), $invoiceId); + $this->assertNotNull($ship); + + $cancel = $payment->cancelAmount(59.5, null, null, 50.0, 9.5); + $this->assertCount(1, $cancel); + $this->assertTrue($payment->isCompleted()); + } + // //