Commit 2747a0fc authored by Vincent Mrose's avatar Vincent Mrose 💬
Browse files

Merge branch 'develop' into 'master'

Develop into Master as Version 1.0.1

See merge request !11
parents 6eca57b5 01b88700
......@@ -7,6 +7,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [1.0.1] - 2019-12-12
### Added
- Partial captures and refunds are now supported
### Changed
- If some inputs in our secure fields are invalid, we now display the errors below them
- The secure fields library was updated based on the official version
### Fixed
- Internet Explorer 11 now finds the selected payment method again
- Some cases of a one cent difference were fixed
- The error handling for reservation errors was improved to stop sideeffects with other modules
- Phone numbers and fax numbers should be less often denied by the API
- An error after the install of the module was fixed regarding the environment setting
- The module is now compatible with Magento 2.3.3
- Any redirect payment method now sets the payment transaction to pending until the user authorized the payment and returned
## [1.0.0] - 2019-10-04
### Fixed
- EXPIRED notifications now increase stock of products when they are cancelling an order
......
......@@ -4,9 +4,9 @@ Magento 2.2.x and 2.3.1+ (not 2.3.0)
This module depends on the CrefoPay PHP Library.
To install that library you will need to add the dependency to the magento composer.json.
This can be done by running `composer require crefopay/php-clientlibrary:1.1.4` in the magento installation folder.
This can be done by running `composer require crefopay/php-clientlibrary:1.1.6` in the magento installation folder.
You can also manually add the line `"crefopay/php-clientlibrary": "1.1.4"` in the composer.json file under the require section.
You can also manually add the line `"crefopay/php-clientlibrary": "1.1.6"` in the composer.json file under the require section.
## Installation
1. Make sure that your system provides all required dependencies (see above)
......@@ -17,4 +17,4 @@ You can also manually add the line `"crefopay/php-clientlibrary": "1.1.4"` in th
6. Run `bin/magento setup:di:compile`
## Known Issues
Please refer to the section in the [Changelog](CHANGELOG.md#known-issues)
Please refer to the section in the [Changelog](CHANGELOG.md#known-issues)
\ No newline at end of file
......@@ -38,6 +38,7 @@ namespace Trilix\CrefoPay\Controller\Mns {
{
/** @var \Magento\Framework\Controller\Result\Raw $result */
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW);
$result->setContents('');
$this->mnsService->acknowledge($_POST);
......
......@@ -166,7 +166,7 @@ class Config extends \Magento\Payment\Gateway\Config\Config
$this->secureFieldsUrl = 'https://sandbox.crefopay.de/secureFields/';
break;
default:
throw new \InvalidArgumentException(sprintf('Invalid value for CrefoPay environment: %s', $environment));
$this->secureFieldsUrl = 'https://sandbox.crefopay.de/secureFields/';
}
return $this->secureFieldsUrl;
......
<?php
declare(strict_types=1);
namespace Trilix\CrefoPay\Gateway\Request\Address;
use Magento\Quote\Model\Quote\Address;
class DataFormatter
{
/**
* @param Address $address
* @return string
*/
public function formatPhoneNumber(Address $address): string
{
$number = preg_replace('/\D/', '', $address->getTelephone());
return substr($number, 0, 1) === '0' ? $number : '0' . $number;
}
/**
* @param Address $address
* @return string
*/
public function formatFaxNumber(Address $address): string
{
$number = preg_replace('/\D/', '', $address->getFax());
return substr($number, 0, 1) === '0' ? $number : '0' . $number;
}
}
......@@ -15,7 +15,7 @@ class AmountBuilder
public function buildFromQuote(Quote $quote): Amount
{
$amount = new Amount();
$amount->setAmount(ceil($quote->getGrandTotal() * 100));
$amount->setAmount(round($quote->getGrandTotal() * 100));
return $amount;
}
......
<?php
declare(strict_types=1);
namespace Trilix\CrefoPay\Gateway\Request;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Model\Customer;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Quote\Model\Quote\Address;
use Upg\Library\Request\Objects\Person;
use Trilix\CrefoPay\Gateway\Request\Address\DataFormatter;
class PersonBuilder
{
/** @var CustomerRepositoryInterface */
private $customerRepository;
/** @var DataFormatter */
private $dataFormatter;
/**
* PersonBuilder constructor.
*
* @param CustomerRepositoryInterface $customerRepository
* @param DataFormatter $dataFormatter
*/
public function __construct(CustomerRepositoryInterface $customerRepository)
{
public function __construct(
CustomerRepositoryInterface $customerRepository,
DataFormatter $dataFormatter
) {
$this->customerRepository = $customerRepository;
$this->dataFormatter = $dataFormatter;
}
/**
......@@ -40,8 +50,8 @@ class PersonBuilder
$person->setName($address->getFirstname());
$person->setSurname($address->getLastname());
$person->setEmail($email);
$person->setFaxNumber($address->getFax());
$person->setPhoneNumber($this->getPhone($address));
$person->setFaxNumber($this->dataFormatter->formatFaxNumber($address));
$person->setPhoneNumber($this->dataFormatter->formatPhoneNumber($address));
$this->setDateOfBirth($person, $customer);
return $person;
......@@ -49,11 +59,10 @@ class PersonBuilder
/**
* @param string $email
*
* @return \Magento\Customer\Api\Data\CustomerInterface|Customer|null
* @return CustomerInterface|null
* @throws LocalizedException
*/
private function getCustomer(string $email)
private function getCustomer(string $email): ?CustomerInterface
{
try {
return $this->customerRepository->get($email);
......@@ -67,7 +76,7 @@ class PersonBuilder
*
* @return string|null
*/
private function getSalutation($customer)
private function getSalutation($customer): ?string
{
if (!$customer) {
return null;
......@@ -80,17 +89,6 @@ class PersonBuilder
}
}
/**
* @param Address $address
*
* @return string
*/
private function getPhone(Address $address): string
{
$number = preg_replace('/\D/', '', $address->getTelephone());
return substr($number, 0, 1) === '0' ? $number : '0' . $number;
}
/**
* @param Person $person
* @param \Magento\Customer\Api\Data\CustomerInterface|Customer|null $customer
......
......@@ -58,6 +58,10 @@ class TransactionService
$txId = $this->generateTransactionId($order);
$payment->setTransactionId($txId);
$payment->setIsTransactionClosed(false);
if ($payment->getMethodInstance()->isGateway()) {
$payment->setIsTransactionPending(true);
}
}
if ($request instanceof UpgRequest\Capture) {
......
<?php
namespace Trilix\CrefoPay\Test\Unit\Controller\Mns;
use Trilix\CrefoPay\Controller\Mns\Sink;
use Magento\Framework\Controller\ResultFactory;
class SinkTest extends \PHPUnit\Framework\TestCase
{
public function testExecute()
{
/** @var \PHPUnit_Framework_MockObject_MockObject $resultMock */
$resultMock = $this->createPartialMock(\Magento\Framework\Controller\Result\Raw::class, ['setContents']);
$resultMock->expects($this->once())->method('setContents')->with('');
/** @var \PHPUnit_Framework_MockObject_MockObject $resultFactoryMock */
$resultFactoryMock = $this->createPartialMock(ResultFactory::class, ['create']);
$resultFactoryMock->expects($this->once())->method('create')
->with(ResultFactory::TYPE_RAW)
->willReturn($resultMock);
/** @var \PHPUnit_Framework_MockObject_MockObject $contextMock */
$contextMock = $this->createPartialMock(\Magento\Framework\App\Action\Context::class, ['getResultFactory']);
$contextMock->expects($this->once())->method('getResultFactory')->willReturn($resultFactoryMock);
$mnsServiceMock = $this->createPartialMock(\Trilix\CrefoPay\Model\Mns\MnsService::class, ['acknowledge']);
$mnsServiceMock->expects($this->once())->method();
$sink = new Sink($contextMock, $mnsServiceMock);
$result = $sink->execute();
$this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $result);
}
}
\ No newline at end of file
......@@ -71,15 +71,9 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
->with('payment/method_code/' . Config::KEY_ENVIRONMENT, ScopeInterface::SCOPE_STORE, null)
->willReturn($environment);
if (!$expectedUrl) {
$this->expectException('InvalidArgumentException');
}
$baseUrl = $this->model->getSecureFieldsUrl();
if ($expectedUrl) {
$this->assertEquals($expectedUrl, $baseUrl);
}
$this->assertSame($expectedUrl, $baseUrl);
}
public function getSecureFieldsUrlDataProvider()
......@@ -87,7 +81,7 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
return [
[Environment::ENVIRONMENT_PRODUCTION, 'https://api.crefopay.de/secureFields/'],
[Environment::ENVIRONMENT_SANDBOX, 'https://sandbox.crefopay.de/secureFields/'],
['uknown environment', null]
['uknown environment', 'https://sandbox.crefopay.de/secureFields/']
];
}
......
<?php
declare(strict_types=1);
namespace Trilix\CrefoPay\Test\Unit\Gateway\Request\Address;
use Magento\Quote\Model\Quote\Address;
use PHPUnit\Framework\MockObject\MockObject;
use Trilix\CrefoPay\Gateway\Request\Address\DataFormatter;
class DataFormatterTest extends \PHPUnit\Framework\TestCase
{
/** @var Address|MockObject */
protected $addressMock;
/** @var DataFormatter */
protected $dataFormatter;
protected function setUp(): void
{
$this->addressMock = $this->getMockBuilder(Address::class)
->disableOriginalConstructor()
->getMock();
$this->dataFormatter = new DataFormatter();
}
/**
* @dataProvider dataProvider
* @param $actualPhoneNumber
* @param $expectedPhoneNumber
*/
public function testFormatPhoneNumber($actualPhoneNumber, $expectedPhoneNumber)
{
$this->addressMock->expects($this->once())
->method('getTelephone')
->willReturn($actualPhoneNumber);
$actualPhoneNumber = $this->dataFormatter->formatPhoneNumber($this->addressMock);
$this->assertSame($expectedPhoneNumber, $actualPhoneNumber);
}
/**
* @dataProvider dataProvider
* @param $actualPhoneNumber
* @param $expectedPhoneNumber
*/
public function testFormatFaxNumber($actualPhoneNumber, $expectedPhoneNumber)
{
$this->addressMock->expects($this->once())
->method('getFax')
->willReturn($actualPhoneNumber);
$actualPhoneNumber = $this->dataFormatter->formatFaxNumber($this->addressMock);
$this->assertSame($expectedPhoneNumber, $actualPhoneNumber);
}
public function dataProvider()
{
return [
['123456789', '0123456789'],
['test-123456789-test1', '01234567891'],
['0test-123456789-test1', '01234567891'],
];
}
}
<?php
declare(strict_types=1);
namespace Trilix\CrefoPay\Test\Unit\Gateway\Request;
use PHPUnit\Framework\MockObject\MockObject;
use Magento\Quote\Model\Quote\Address;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Trilix\CrefoPay\Gateway\Request\Address\DataFormatter;
use Upg\Library\Request\Objects\Person;
use Trilix\CrefoPay\Gateway\Request\PersonBuilder;
class PersonBuilderTest extends \PHPUnit\Framework\TestCase
{
/** @var CustomerRepositoryInterface|MockObject */
protected $customerRepositoryMock;
/** @var Address|MockObject */
protected $addressMock;
/** @var CustomerInterface|MockObject */
protected $customerMock;
/** @var DataFormatter|MockObject */
protected $dataFormatterMock;
/** @var PersonBuilder */
protected $personBuilder;
protected function setUp(): void
{
$this->customerRepositoryMock = $this->getMockBuilder(CustomerRepositoryInterface::class)
->getMock();
$this->addressMock = $this->getMockBuilder(Address::class)
->disableOriginalConstructor()
->getMock();
$this->customerMock = $this->getMockBuilder(CustomerInterface::class)
->disableOriginalConstructor()
->getMock();
$this->dataFormatterMock = $this->getMockBuilder(DataFormatter::class)
->getMock();
$this->personBuilder = new PersonBuilder($this->customerRepositoryMock, $this->dataFormatterMock);
}
public function testBuildWhenThereIsNoCustomerWithProvidedEmail()
{
$email = 'test@mail.com';
$this->customerRepositoryMock->expects($this->once())
->method('get')
->with($this->equalTo($email))
->willReturn(null);
$this->addressMock->expects($this->once())
->method('getFirstName')
->willReturn('firstname');
$this->addressMock->expects($this->once())
->method('getLastName')
->willReturn('lastname');
$this->dataFormatterMock->expects($this->once())
->method('formatFaxNumber')
->willReturn('123');
$this->dataFormatterMock->expects($this->once())
->method('formatPhoneNumber')
->willReturn('123456789');
$actualObject = $this->personBuilder->build($this->addressMock, $email);
$this->assertEquals('firstname', $actualObject->getName());
$this->assertEquals('lastname', $actualObject->getSurname());
$this->assertNull($actualObject->getSalutation());
$this->assertEquals('123', $actualObject->getFaxNumber());
$this->assertEquals('123456789', $actualObject->getPhoneNumber());
$this->assertNull($actualObject->getDateOfBirth());
}
/**
* @dataProvider salutationDataProvider
*/
public function testBuild($gender, $expected)
{
$email = 'test@mail.com';
$this->customerRepositoryMock->expects($this->once())
->method('get')
->with($this->equalTo($email))
->willReturn($this->customerMock);
$this->customerMock->expects($this->once())
->method('getGender')
->willReturn($gender);
$this->customerMock->expects($this->atLeastOnce())
->method('getDob')
->willReturn('11.01.2001');
$this->addressMock->expects($this->once())
->method('getFirstName')
->willReturn('firstname');
$this->addressMock->expects($this->once())
->method('getLastName')
->willReturn('lastname');
$this->dataFormatterMock->expects($this->once())
->method('formatFaxNumber')
->willReturn('123');
$this->dataFormatterMock->expects($this->once())
->method('formatPhoneNumber')
->willReturn('123456789');
$actualObject = $this->personBuilder->build($this->addressMock, $email);
$this->assertEquals('firstname', $actualObject->getName());
$this->assertEquals('lastname', $actualObject->getSurname());
$this->assertEquals($expected, $actualObject->getSalutation());
$this->assertEquals('123', $actualObject->getFaxNumber());
$this->assertEquals('123456789', $actualObject->getPhoneNumber());
$this->assertEquals(new \DateTime('11.01.2001'), $actualObject->getDateOfBirth());
}
public function salutationDataProvider()
{
return [
[1, Person::SALUTATIONMALE],
[2, Person::SALUTATIONFEMALE],
['other', null],
];
}
}
......@@ -13,60 +13,106 @@ class TransactionServiceTest extends \PHPUnit\Framework\TestCase
{
public function testAddTransactionReserve()
{
$paymentDOMock = $this->getPaymentDOMock();
$paymentMock = $this->getPaymentMock(true);
$paymentDOMock->getPayment()
$paymentMock
->expects($this->once())
->method('setIsTransactionClosed')
->with(false);
$paymentMock
->expects($this->once())
->method('setTransactionId')
->with($this->callback(function ($hash) {
return strlen($hash) === 40; // sha1
}));
$paymentMock
->expects($this->once())
->method('setIsTransactionPending')
->with(true);
$responseMock = $this->getResponseMock($paymentMock);
$paymentDOMock = $this->getPaymentDOMock($paymentMock);
/** @var TransactionService $uut */
$uut = (new ObjectManagerHelper($this))->getObject(TransactionService::class);
$uut->addTransaction($paymentDOMock, $this->createMock(UpgRequest\Reserve::class), $this->getResponseMock());
$uut->addTransaction($paymentDOMock, $this->createMock(UpgRequest\Reserve::class), $responseMock);
}
public function testAddTransactionCapture()
{
$paymentDOMock = $this->getPaymentDOMock();
$captureId = 'C4P7UR31D';
$paymentMock = $this->getPaymentMock();
$requestMock = $this->createConfiguredMock(UpgRequest\Capture::class, ['getCaptureId' => $captureId]);
$paymentDOMock->getPayment()
$paymentMock
->expects($this->once())
->method('setTransactionAdditionalInfo')
->with('capture_id', null);
->with('capture_id', $captureId);
/** @var TransactionService $uut */
$uut = (new ObjectManagerHelper($this))->getObject(TransactionService::class);
$uut->addTransaction($paymentDOMock, $this->createMock(UpgRequest\Capture::class), $this->getResponseMock());
$uut->addTransaction($this->getPaymentDOMock($paymentMock), $requestMock, $this->getResponseMock());
}
private function getResponseMock($paymentMock = null)
{
$transactionAdditionalInfo = $paymentMock ? ['a' => 'b', 'c' => 'd'] : [];
if ($paymentMock) {
$paymentMock
->expects($this->exactly(2))
->method('setTransactionAdditionalInfo')
->withConsecutive(['a', 'b'], ['c', 'd']);
}
return $responseMock = $this->createConfiguredMock(SuccessResponse::class, ['getAllData' => $transactionAdditionalInfo]);
}
private function getResponseMock()
private function getPaymentDOMock($paymentMock)
{
$responseMock = $this->createMock(SuccessResponse::class);
$paymentDOMock = $this->createPartialMock(PaymentDataObjectInterface::class, ['getPayment', 'getOrder']);
$responseMock
$paymentDOMock
->expects($this->any())
->method('getAllData')
->willReturn([]);
->method('getPayment')
->willReturn($paymentMock);
return $responseMock;
return $paymentDOMock;
}
private function getPaymentDOMock()
private function getPaymentMock($isGateway = false)
{
$paymentMock = $this->createMock(Payment::class);
$paymentMethodInstanceMock = $this->createMock(\Magento\Payment\Model\MethodInterface::class);
$paymentMethodInstanceMock
->expects($this->any())
->method('isGateway')
->willReturn($isGateway);
$paymentMock = $this->createPartialMock(Payment::class,
[
'getOrder',
'getMethodInstance',
'setTransactionAdditionalInfo',
'setIsTransactionClosed',
'setTransactionId',
'setIsTransactionPending',
]
);
$paymentMock
->expects($this->any())
->method('getOrder')
->willReturn((new ObjectManagerHelper($this))->getObject(Order::class));
$paymentDOMock = $this->createMock(PaymentDataObjectInterface::class);
$paymentDOMock
$paymentMock
->expects($this->any())
->method('getPayment')