diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9e0f36..fe4e1008 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added * Example for Payment Type `Przelewy24`. +* Enabled recurring payment for SEPA direct debit (guaranteed). ### Fixed -* A bug which led to an error when trying to cancel the initial transaction of a charged invoice. * Composer: PHP version constraint. * Several minor issues. @@ -25,6 +25,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * Move method doc blocks to service interfaces. * Remove dead code. +## [1.2.5.1][1.2.5.1] + +### Fix +* A bug which led to an error when trying to cancel the initial transaction of a charged invoice. + ## [1.2.5.0][1.2.5.0] ### Added @@ -79,6 +84,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * Refactor deprecation notices. * Refactored and extended unit tests. * Test keypair can now be set via environment variables. +* Activate recurring payment for `SEPA Direct Debit (guaranteed)`. ## [1.2.2.0][1.2.2.0] @@ -356,4 +362,5 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a [1.2.3.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.2.0..1.2.3.0 [1.2.4.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.3.0..1.2.4.0 [1.2.5.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.4.0..1.2.5.0 -[1.2.6.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.5.0..1.2.6.0 +[1.2.5.1]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.5.0..1.2.5.1 +[1.2.6.0]: https://github.com/heidelpay/heidelpayPHP/compare/1.2.5.1..1.2.6.0 diff --git a/README.md b/README.md index df0c3502..5094b82e 100755 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Please refer to the following documentation for installation instructions and us * PayPal + Recurring * Prepayment * Przelewy24 -* SEPA direct debit (guaranteed) +* SEPA direct debit (guaranteed) + Recurring * SOFORT * EPS * FlexiPay® Direct (PIS) diff --git a/src/Constants/ApiResponseCodes.php b/src/Constants/ApiResponseCodes.php index eb05a4d7..76392b8c 100755 --- a/src/Constants/ApiResponseCodes.php +++ b/src/Constants/ApiResponseCodes.php @@ -69,6 +69,8 @@ class ApiResponseCodes const API_ERROR_CUSTOMER_CAN_NOT_BE_FOUND = 'API.500.100.100'; const API_ERROR_REQUEST_DATA_IS_INVALID = 'API.500.300.999'; const API_ERROR_RECURRING_PAYMENT_NOT_SUPPORTED = 'API.500.550.004'; + const API_ERROR_ACTIVATE_RECURRING_VIA_TRANSACTION = 'API.500.550.005'; + const API_ERROR_RECURRING_ALREADY_ACTIVE = 'API.500.550.006'; const API_ERROR_WEBHOOK_EVENT_ALREADY_REGISTERED = 'API.510.310.009'; const API_ERROR_WEBHOOK_CAN_NOT_BE_FOUND = 'API.510.310.008'; const API_ERROR_BASKET_ITEM_IMAGE_INVALID_URL = 'API.600.630.004'; diff --git a/src/Resources/PaymentTypes/SepaDirectDebit.php b/src/Resources/PaymentTypes/SepaDirectDebit.php index 722bff92..33b40783 100755 --- a/src/Resources/PaymentTypes/SepaDirectDebit.php +++ b/src/Resources/PaymentTypes/SepaDirectDebit.php @@ -26,11 +26,13 @@ use heidelpayPHP\Traits\CanDirectCharge; use heidelpayPHP\Traits\CanPayout; +use heidelpayPHP\Traits\CanRecur; class SepaDirectDebit extends BasePaymentType { use CanDirectCharge; use CanPayout; + use CanRecur; /** @var string $iban */ protected $iban; diff --git a/src/Resources/PaymentTypes/SepaDirectDebitGuaranteed.php b/src/Resources/PaymentTypes/SepaDirectDebitGuaranteed.php index 59dc86c7..5d99819d 100755 --- a/src/Resources/PaymentTypes/SepaDirectDebitGuaranteed.php +++ b/src/Resources/PaymentTypes/SepaDirectDebitGuaranteed.php @@ -26,11 +26,13 @@ use heidelpayPHP\Traits\CanDirectChargeWithCustomer; use heidelpayPHP\Traits\CanPayoutWithCustomer; +use heidelpayPHP\Traits\CanRecur; class SepaDirectDebitGuaranteed extends BasePaymentType { use CanDirectChargeWithCustomer; use CanPayoutWithCustomer; + use CanRecur; /** @var string $iban */ protected $iban; diff --git a/src/Resources/Recurring.php b/src/Resources/Recurring.php index 28bd4b2d..413db441 100644 --- a/src/Resources/Recurring.php +++ b/src/Resources/Recurring.php @@ -39,7 +39,7 @@ class Recurring extends AbstractHeidelpayResource /** @var string $returnUrl */ protected $returnUrl; - /** @var string $redirectUrl */ + /** @var string|null $redirectUrl */ protected $redirectUrl; /** @var string $paymentTypeId */ @@ -96,19 +96,19 @@ public function setPaymentTypeId(string $paymentTypeId): Recurring } /** - * @return string + * @return string|null */ - public function getRedirectUrl(): string + public function getRedirectUrl() { return $this->redirectUrl; } /** - * @param string $redirectUrl + * @param string|null $redirectUrl * * @return Recurring */ - protected function setRedirectUrl(string $redirectUrl): Recurring + protected function setRedirectUrl($redirectUrl): Recurring { $this->redirectUrl = $redirectUrl; return $this; diff --git a/src/Services/EnvironmentService.php b/src/Services/EnvironmentService.php index 1d9d7356..8cd265f9 100755 --- a/src/Services/EnvironmentService.php +++ b/src/Services/EnvironmentService.php @@ -80,23 +80,29 @@ public static function getTimeout(): int * Returns the private key string set via environment variable. * Returns the default key if the environment variable is not set. * + * @param bool $non3ds + * * @return string */ - public function getTestPrivateKey(): string + public function getTestPrivateKey($non3ds = false): string { - $key = $_SERVER[self::ENV_VAR_TEST_PRIVATE_KEY] ?? ''; - return empty($key) ? self::DEFAULT_TEST_PRIVATE_KEY : $key; + $variableName = self::ENV_VAR_TEST_PRIVATE_KEY . ($non3ds ? '_NON_3DS' : ''); + $key = $_SERVER[$variableName] ?? ''; + return empty($key) && !$non3ds ? self::DEFAULT_TEST_PRIVATE_KEY : $key; } /** * Returns the public key string set via environment variable. * Returns the default key if the environment variable is not set. * + * @param bool $non3ds + * * @return string */ - public function getTestPublicKey(): string + public function getTestPublicKey($non3ds = false): string { - $key = $_SERVER[self::ENV_VAR_TEST_PUBLIC_KEY] ?? ''; - return empty($key) ? self::DEFAULT_TEST_PUBLIC_KEY : $key; + $variableName = self::ENV_VAR_TEST_PUBLIC_KEY . ($non3ds ? '_NON_3DS' : ''); + $key = $_SERVER[$variableName] ?? ''; + return empty($key) && !$non3ds ? self::DEFAULT_TEST_PUBLIC_KEY : $key; } } diff --git a/test/BasePaymentTest.php b/test/BasePaymentTest.php index 34faf0af..bbdc8e3a 100755 --- a/test/BasePaymentTest.php +++ b/test/BasePaymentTest.php @@ -129,11 +129,11 @@ public function assertTransactionResourceHasBeenCreated($transactionType) /** * Asserts whether the given transaction was successful. * - * @param AbstractTransactionType $transaction + * @param AbstractTransactionType|Recurring $transaction * * @throws AssertionFailedError */ - protected function assertSuccess(AbstractTransactionType $transaction) + protected function assertSuccess($transaction) { $this->assertTrue($transaction->isSuccess()); $this->assertFalse($transaction->isPending()); @@ -143,11 +143,11 @@ protected function assertSuccess(AbstractTransactionType $transaction) /** * Asserts whether the given transaction was a failure. * - * @param AbstractTransactionType $transaction + * @param AbstractTransactionType|Recurring $transaction * * @throws AssertionFailedError */ - protected function assertError(AbstractTransactionType $transaction) + protected function assertError($transaction) { $this->assertFalse($transaction->isSuccess()); $this->assertFalse($transaction->isPending()); diff --git a/test/integration/PaymentTest.php b/test/integration/PaymentTest.php index 037e619c..7b865d20 100755 --- a/test/integration/PaymentTest.php +++ b/test/integration/PaymentTest.php @@ -28,6 +28,7 @@ use heidelpayPHP\Constants\ApiResponseCodes; use heidelpayPHP\Exceptions\HeidelpayApiException; use heidelpayPHP\Resources\Payment; +use heidelpayPHP\Resources\PaymentTypes\Card; use heidelpayPHP\Resources\PaymentTypes\Paypal; use heidelpayPHP\Resources\TransactionTypes\Authorization; use heidelpayPHP\Resources\TransactionTypes\Charge; @@ -196,45 +197,74 @@ public function chargePaymentShouldThrowErrorOnNonPaymentId() } /** - * Verify an Exception is thrown if the orderId already exists. + * Verify a payment is fetched by orderId if the id is not set. * * @test * * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request. * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. */ - public function apiShouldReturnErrorIfOrderIdAlreadyExists() + public function paymentShouldBeFetchedByOrderIdIfIdIsNotSet() { $orderId = str_replace(' ', '', microtime()); - $paypal = $this->heidelpay->createPaymentType(new Paypal()); $authorization = $this->heidelpay->authorize(100.00, 'EUR', $paypal, 'http://heidelpay.com', null, $orderId, null, null, false); - $this->assertNotEmpty($authorization); + $payment = $authorization->getPayment(); + $fetchedPayment = $this->heidelpay->fetchPaymentByOrderId($orderId); + + $this->assertNotSame($payment, $fetchedPayment); + $this->assertEquals($payment->expose(), $fetchedPayment->expose()); + } - $paypal2 = $this->heidelpay->createPaymentType(new Paypal()); + /** + * Verify orderId does not need to be unique. + * + * @test + * + * @throws HeidelpayApiException + * @throws RuntimeException + */ + public function shouldAllowNonUniqueOrderId() + { + $orderId = self::generateRandomId(); - $this->expectException(HeidelpayApiException::class); - $this->expectExceptionCode(ApiResponseCodes::API_ERROR_ORDER_ID_ALREADY_IN_USE); - $this->heidelpay->authorize(101.00, 'EUR', $paypal2, 'http://heidelpay.com', null, $orderId, null, null, false); + /** @var Card $card */ + $card = $this->heidelpay->createPaymentType($this->createCardObject()); + $card->charge(1023, 'EUR', self::RETURN_URL, null, $orderId); + + try { + /** @var Card $card2 */ + $card2 = $this->heidelpay->createPaymentType($this->createCardObject()); + $card2->charge(1023, 'EUR', self::RETURN_URL, null, $orderId); + $this->assertTrue(true); + } catch (HeidelpayApiException $e) { + $this->assertTrue(false, "No exception expected here. ({$e->getMerchantMessage()})"); + } } /** - * Verify a payment is fetched by orderId if the id is not set. + * Verify invoiceId does not need to be unique. * * @test * - * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request. - * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + * @throws HeidelpayApiException + * @throws RuntimeException */ - public function paymentShouldBeFetchedByOrderIdIfIdIsNotSet() + public function shouldAllowNonUniqueInvoiceId() { - $orderId = str_replace(' ', '', microtime()); - $paypal = $this->heidelpay->createPaymentType(new Paypal()); - $authorization = $this->heidelpay->authorize(100.00, 'EUR', $paypal, 'http://heidelpay.com', null, $orderId, null, null, false); - $payment = $authorization->getPayment(); - $fetchedPayment = $this->heidelpay->fetchPaymentByOrderId($orderId); + $invoiceId = self::generateRandomId(); - $this->assertNotSame($payment, $fetchedPayment); - $this->assertEquals($payment->expose(), $fetchedPayment->expose()); + /** @var Card $card */ + $card = $this->heidelpay->createPaymentType($this->createCardObject()); + $card->charge(1023, 'EUR', self::RETURN_URL, null, null, null, null, null, $invoiceId); + + try { + /** @var Card $card2 */ + $card2 = $this->heidelpay->createPaymentType($this->createCardObject()); + $card2->charge(1023, 'EUR', self::RETURN_URL, null, null, null, null, null, $invoiceId); + $this->assertTrue(true); + } catch (HeidelpayApiException $e) { + $this->assertTrue(false, "No exception expected here. ({$e->getMerchantMessage()})"); + } } } diff --git a/test/integration/RecurringPaymentTest.php b/test/integration/RecurringPaymentTest.php index 43c0a3f4..e1661996 100644 --- a/test/integration/RecurringPaymentTest.php +++ b/test/integration/RecurringPaymentTest.php @@ -26,8 +26,12 @@ use heidelpayPHP\Constants\ApiResponseCodes; use heidelpayPHP\Exceptions\HeidelpayApiException; +use heidelpayPHP\Heidelpay; use heidelpayPHP\Resources\PaymentTypes\Card; use heidelpayPHP\Resources\PaymentTypes\Paypal; +use heidelpayPHP\Resources\PaymentTypes\SepaDirectDebit; +use heidelpayPHP\Resources\PaymentTypes\SepaDirectDebitGuaranteed; +use heidelpayPHP\Services\EnvironmentService; use heidelpayPHP\test\BasePaymentTest; use PHPUnit\Framework\Exception; use RuntimeException; @@ -81,20 +85,26 @@ public function recurringForCardWith3dsShouldReturnAttributes() * * @throws HeidelpayApiException A HeidelpayApiException is thrown if there is an error returned on API-request. * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. - * - * @group skip */ public function recurringForCardWithout3dsShouldActivateRecurringAtOnce() { + $privateKey = (new EnvironmentService())->getTestPrivateKey(true); + if (empty($privateKey)) { + $this->markTestIncomplete('No non 3ds private key set'); + } + $heidelpay = new Heidelpay($privateKey); + + $heidelpay->setDebugMode(true)->setDebugHandler($this->heidelpay->getDebugHandler()); + /** @var Card $card */ - $card = $this->heidelpay->createPaymentType($this->createCardObject()->set3ds(false)); + $card = $heidelpay->createPaymentType($this->createCardObject()->set3ds(false)); $this->assertFalse($card->isRecurring()); $recurring = $card->activateRecurring('https://dev.heidelpay.com'); - $this->assertPending($recurring); + $this->assertSuccess($recurring); /** @var Card $fetchedCard */ - $fetchedCard = $this->heidelpay->fetchPaymentType($card->getId()); + $fetchedCard = $heidelpay->fetchPaymentType($card->getId()); $this->assertTrue($fetchedCard->isRecurring()); } @@ -114,4 +124,44 @@ public function paypalShouldBeAbleToActivateRecurringPayments() $this->assertPending($recurring); $this->assertNotEmpty($recurring->getReturnUrl()); } + + /** + * Verify sepa direct debit can activate recurring payments. + * + * @test + * + * @throws RuntimeException + * @throws HeidelpayApiException + */ + public function sepaDirectDebitShouldBeAbleToActivateRecurringPayments() + { + /** @var SepaDirectDebit $dd */ + $dd = $this->heidelpay->createPaymentType(new SepaDirectDebit('DE89370400440532013000')); + $this->assertFalse($dd->isRecurring()); + $dd->charge(10.0, 'EUR', self::RETURN_URL); + $dd = $this->heidelpay->fetchPaymentType($dd->getId()); + $this->assertTrue($dd->isRecurring()); + + $this->expectException(HeidelpayApiException::class); + $this->expectExceptionCode(ApiResponseCodes::API_ERROR_RECURRING_ALREADY_ACTIVE); + $this->heidelpay->activateRecurringPayment($dd, self::RETURN_URL); + } + + /** + * Verify sepa direct debit guaranteed can activate recurring payments. + * + * @test + * + * @throws RuntimeException + * @throws HeidelpayApiException + */ + public function sepaDirectDebitGuaranteedShouldBeAbleToActivateRecurringPayments() + { + /** @var SepaDirectDebitGuaranteed $ddg */ + $ddg = $this->heidelpay->createPaymentType(new SepaDirectDebitGuaranteed('DE89370400440532013000')); + + $this->expectException(HeidelpayApiException::class); + $this->expectExceptionCode(ApiResponseCodes::API_ERROR_ACTIVATE_RECURRING_VIA_TRANSACTION); + $ddg->activateRecurring('https://dev.heidelpay.com'); + } }