Skip to content

Commit

Permalink
PAYOSWSXP-130: credit-card: add card-holder
Browse files Browse the repository at this point in the history
  • Loading branch information
rommelfreddy committed Jul 18, 2024
1 parent 4dc108d commit dc3533d
Show file tree
Hide file tree
Showing 17 changed files with 169 additions and 20 deletions.
23 changes: 20 additions & 3 deletions src/Components/CardRepository/CardRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(private readonly EntityRepository $cardRepository)

public function saveCard(
CustomerEntity $customer,
string $cardHolder,
string $truncatedCardPan,
string $pseudoCardPan,
string $cardType,
Expand All @@ -39,6 +40,7 @@ public function saveCard(

$data = [
'id' => $card === null ? Uuid::randomHex() : $card->getId(),
'cardHolder' => $cardHolder,
'pseudoCardPan' => $pseudoCardPan,
'truncatedCardPan' => $truncatedCardPan,
'cardType' => $cardType,
Expand Down Expand Up @@ -96,20 +98,35 @@ public function removeAllCardsForCustomer(CustomerEntity $customer, Context $con
}

public function getExistingCard(
CustomerEntity $customer,
CustomerEntity|string $customer,
string $pseudoCardPan,
Context $context
): ?PayonePaymentCardEntity {
$criteria = new Criteria();

$criteria->addFilter(
new EqualsFilter('payone_payment_card.pseudoCardPan', $pseudoCardPan),
new EqualsFilter('payone_payment_card.customerId', $customer->getId())
new EqualsFilter('pseudoCardPan', $pseudoCardPan),
new EqualsFilter('customerId', \is_string($customer) ? $customer : $customer->getId())
);

/** @var PayonePaymentCardEntity|null $card */
$card = $this->cardRepository->search($criteria, $context)->first();

return $card;
}

/**
* TODO-card-holder-requirement: remove this method (please see credit-card handler)
* @deprecated
*/
public function saveMissingCardHolder(string $cardId, string $customerId, mixed $cardHolder, Context $context): void
{
$this->cardRepository->upsert([
[
'id' => $cardId,
'customerId' => $customerId,
'cardHolder' => $cardHolder,
],
], $context);
}
}
9 changes: 8 additions & 1 deletion src/Components/CardRepository/CardRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface CardRepositoryInterface
{
public function saveCard(
CustomerEntity $customer,
string $cardHolder,
string $truncatedCardPan,
string $pseudoCardPan,
string $cardType,
Expand All @@ -37,8 +38,14 @@ public function removeAllCardsForCustomer(
): void;

public function getExistingCard(
CustomerEntity $customer,
CustomerEntity|string $customer,
string $pseudoCardPan,
Context $context
): ?PayonePaymentCardEntity;

/**
* TODO-card-holder-requirement: remove this method (please see credit-card handler)
* @deprecated
*/
public function saveMissingCardHolder(string $cardId, string $customerId, mixed $cardHolder, Context $context): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct;
use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException;
use Shopware\Core\Checkout\Payment\PaymentException;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
Expand All @@ -26,9 +27,9 @@ public static function isCapturable(array $transactionData, array $payoneTransAc
return static::isTransactionAppointedAndCompleted($transactionData) || static::matchesIsCapturableDefaults($transactionData);
}

public function getValidationDefinitions(SalesChannelContext $salesChannelContext): array
public function getValidationDefinitions(DataBag $dataBag, SalesChannelContext $salesChannelContext): array
{
return array_merge(parent::getValidationDefinitions($salesChannelContext), [
return array_merge(parent::getValidationDefinitions($dataBag, $salesChannelContext), [
RequestConstants::WORK_ORDER_ID => [new NotBlank()],
RequestConstants::CART_HASH => [new NotBlank()],
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ protected function defineFields(): FieldCollection
(new IdField('id', 'id'))->setFlags(new PrimaryKey(), new Required()),

(new FkField('customer_id', 'customerId', CustomerDefinition::class))->addFlags(new Required()),
(new StringField('card_holder', 'cardHolder'))->setFlags(new Required()),

$pseudoCardPanField,
(new StringField('truncated_card_pan', 'truncatedCardPan'))->setFlags(new Required()),
Expand Down
10 changes: 10 additions & 0 deletions src/DataAbstractionLayer/Entity/Card/PayonePaymentCardEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class PayonePaymentCardEntity extends Entity

protected string $customerId;

/**
* @since 6.2.0
*/
protected string $cardHolder;

public function getPseudoCardPan(): string
{
return $this->pseudoCardPan;
Expand Down Expand Up @@ -83,4 +88,9 @@ public function getCustomerId(): string
{
return $this->customerId;
}

public function getCardHolder(): string
{
return $this->cardHolder;
}
}
2 changes: 1 addition & 1 deletion src/DependencyInjection/requestParameter/builder.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@

<!-- Credit Card -->
<service id="PayonePayment\Payone\RequestParameter\Builder\CreditCard\AuthorizeRequestParameterBuilder"
parent="PayonePayment\Payone\RequestParameter\Builder\AbstractRequestParameterBuilder">
parent="PayonePayment\Payone\RequestParameter\Builder\AbstractRequestParameterBuilder" autowire="true">

<tag name="payone_request_builder"/>
</service>
Expand Down
30 changes: 30 additions & 0 deletions src/Migration/Migration1718642595CreditCardAddCardHolder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace PayonePayment\Migration;

use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;

class Migration1718642595CreditCardAddCardHolder extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1_718_642_595;
}

public function update(Connection $connection): void
{
$connection->executeStatement(
<<<SQL
ALTER TABLE `payone_payment_card`
ADD `card_holder` VARCHAR(255) NOT NULL AFTER `customer_id`;
SQL
);
}

public function updateDestructive(Connection $connection): void
{
}
}
40 changes: 38 additions & 2 deletions src/PaymentHandler/PayoneCreditCardPaymentHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@
use PayonePayment\Components\DataHandler\Transaction\TransactionDataHandlerInterface;
use PayonePayment\Components\PaymentStateHandler\PaymentStateHandlerInterface;
use PayonePayment\Components\TransactionStatus\TransactionStatusService;
use PayonePayment\DataAbstractionLayer\Entity\Card\PayonePaymentCardEntity;
use PayonePayment\Payone\Client\PayoneClientInterface;
use PayonePayment\Payone\RequestParameter\Builder\AbstractRequestParameterBuilder;
use PayonePayment\Payone\RequestParameter\RequestParameterFactory;
use PayonePayment\Struct\PaymentTransaction;
use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Contracts\Translation\TranslatorInterface;

class PayoneCreditCardPaymentHandler extends AbstractAsynchronousPayonePaymentHandler
Expand All @@ -31,6 +34,7 @@ class PayoneCreditCardPaymentHandler extends AbstractAsynchronousPayonePaymentHa
final public const REQUEST_PARAM_CARD_EXPIRE_DATE = 'cardExpireDate';
final public const REQUEST_PARAM_CARD_TYPE = 'cardType';
final public const REQUEST_PARAM_TRUNCATED_CARD_PAN = 'truncatedCardPan';
final public const REQUEST_PARAM_CARD_HOLDER = 'cardHolder';

public function __construct(
ConfigReaderInterface $configReader,
Expand Down Expand Up @@ -59,13 +63,36 @@ public function __construct(
);
}

public function getValidationDefinitions(DataBag $dataBag, SalesChannelContext $salesChannelContext): array
{
$definitions = parent::getValidationDefinitions($dataBag, $salesChannelContext);

// Please note: this is field is only required, for that case, that the card has been already saved, but no
// card-holder has been saved (because this field was added in a later version)
// with that we want to make sure, that a card-holder is always present.
// if a card holder as been already saved, the submitted value will be ignored.
// if no card holder has been saved, and no values has been submitted, the next line will cause a validation-error
// TODO in the far future: move this into the if-block for the case, if the card has not been saved.
// search for the following to-do reference, to adjust the related code: TODO-card-holder-requirement
$definitions[self::REQUEST_PARAM_CARD_HOLDER] = [new NotBlank()];

if (empty($dataBag->get(self::REQUEST_PARAM_SAVED_PSEUDO_CARD_PAN))) {
$definitions[self::REQUEST_PARAM_PSEUDO_CARD_PAN] = [new NotBlank()];
$definitions[self::REQUEST_PARAM_TRUNCATED_CARD_PAN] = [new NotBlank()];
$definitions[self::REQUEST_PARAM_CARD_EXPIRE_DATE] = [new NotBlank()];
$definitions[self::REQUEST_PARAM_CARD_TYPE] = [new NotBlank()];
}

return $definitions;
}

public static function isCapturable(array $transactionData, array $payoneTransActionData): bool
{
if (static::isNeverCapturable($payoneTransActionData)) {
return false;
}

$txAction = isset($transactionData['txaction']) ? strtolower((string) $transactionData['txaction']) : null;
$txAction = isset($transactionData['txaction']) ? strtolower((string)$transactionData['txaction']) : null;

if ($txAction === TransactionStatusService::ACTION_APPOINTED) {
return true;
Expand Down Expand Up @@ -106,6 +133,9 @@ protected function handleResponse(
$customer = $salesChannelContext->getCustomer();
$savedPseudoCardPan = $dataBag->get(self::REQUEST_PARAM_SAVED_PSEUDO_CARD_PAN);

// TODO-card-holder-requirement: move the next line into the if-block if the $savedPseudoCardPan is empty (please see credit-card handler)
$cardHolder = $dataBag->get(self::REQUEST_PARAM_CARD_HOLDER);

if (empty($savedPseudoCardPan)) {
$truncatedCardPan = $dataBag->get(self::REQUEST_PARAM_TRUNCATED_CARD_PAN);
$cardExpireDate = $dataBag->get(self::REQUEST_PARAM_CARD_EXPIRE_DATE);
Expand All @@ -117,6 +147,7 @@ protected function handleResponse(
if (!empty($expiresAt) && $customer !== null && $saveCreditCard) {
$this->cardRepository->saveCard(
$customer,
$cardHolder,
$truncatedCardPan,
$pseudoCardPan,
$cardType,
Expand All @@ -134,6 +165,11 @@ protected function handleResponse(
$salesChannelContext->getContext()
);

// TODO-card-holder-requirement: remove this if-statement incl. content (please see credit-card handler)
if ($savedCard instanceof PayonePaymentCardEntity && empty($savedCard->getCardHolder())) {
$this->cardRepository->saveMissingCardHolder($savedCard->getId(), $customer->getId(), $cardHolder, $salesChannelContext->getContext());
}

$cardType = $savedCard ? $savedCard->getCardType() : '';
}
}
Expand All @@ -152,7 +188,7 @@ protected function handleResponse(

protected function getRedirectResponse(SalesChannelContext $context, array $request, array $response): RedirectResponse
{
if (strtolower((string) $response['status']) === 'redirect') {
if (strtolower((string)$response['status']) === 'redirect') {
return new RedirectResponse($response['redirecturl']);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,49 @@

namespace PayonePayment\Payone\RequestParameter\Builder\CreditCard;

use PayonePayment\Components\CardRepository\CardRepository;
use PayonePayment\PaymentHandler\PayoneCreditCardPaymentHandler;
use PayonePayment\Payone\RequestParameter\Builder\AbstractRequestParameterBuilder;
use PayonePayment\Payone\RequestParameter\Builder\RequestBuilderServiceAccessor;
use PayonePayment\Payone\RequestParameter\Struct\AbstractRequestParameterStruct;
use PayonePayment\Payone\RequestParameter\Struct\PaymentTransactionStruct;

class AuthorizeRequestParameterBuilder extends AbstractRequestParameterBuilder
{
public function __construct(
RequestBuilderServiceAccessor $serviceAccessor,
private readonly CardRepository $cardRepository
) {
parent::__construct($serviceAccessor);
}

/**
* @param PaymentTransactionStruct $arguments
*/
public function getRequestParameter(AbstractRequestParameterStruct $arguments): array
{
$cardHolder = $arguments->getRequestData()->get(PayoneCreditCardPaymentHandler::REQUEST_PARAM_CARD_HOLDER);
$cardType = $arguments->getRequestData()->get(PayoneCreditCardPaymentHandler::REQUEST_PARAM_CARD_TYPE);
$pseudoCardPan = $arguments->getRequestData()->get(PayoneCreditCardPaymentHandler::REQUEST_PARAM_PSEUDO_CARD_PAN);
$savedPseudoCardPan = $arguments->getRequestData()->get(PayoneCreditCardPaymentHandler::REQUEST_PARAM_SAVED_PSEUDO_CARD_PAN);

if (!empty($savedPseudoCardPan)) {
$pseudoCardPan = $savedPseudoCardPan;
$salesChannelContext = $arguments->getSalesChannelContext();
if (!empty($savedPseudoCardPan) && !empty($salesChannelContext->getCustomerId())) {
$savedCard = $this->cardRepository->getExistingCard(
$salesChannelContext->getCustomerId(),
$savedPseudoCardPan,
$salesChannelContext->getContext()
);
$pseudoCardPan = $savedCard?->getPseudoCardPan() ?: '';
$cardHolder = $savedCard?->getCardHolder() ?: $cardHolder;
}

return [
'clearingtype' => self::CLEARING_TYPE_CREDIT_CARD,
'request' => $arguments->getAction(),
'pseudocardpan' => $pseudoCardPan,
'cardtype' => $cardType,
'cardholder' => $cardHolder,
];
}

Expand All @@ -39,6 +57,6 @@ public function supports(AbstractRequestParameterStruct $arguments): bool
}

return $arguments->getPaymentMethod() === PayoneCreditCardPaymentHandler::class
&& in_array($arguments->getAction(), [self::REQUEST_ACTION_PREAUTHORIZE, self::REQUEST_ACTION_AUTHORIZE]);
&& \in_array($arguments->getAction(), [self::REQUEST_ACTION_PREAUTHORIZE, self::REQUEST_ACTION_AUTHORIZE], true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ describe('Credit Card', () => {
cy.checkoutConfirmAndComplete(
() => {
cy.get('@creditCardPan').then((storedPan) => {
cy.get('#savedpseudocardpan').select(storedPan)
cy.get('#savedpseudocardpan').select(storedPan);
cy.get('.credit-card-input').should('not.be.visible');
})
});
});

function fillIframe(iban = '4111111111111111') {
cy.get('#creditCardHolder').type('credit card holder');
setIframeValue('cardpan', 'input', iban);
setIframeValue('cardcvc2', 'input', '123');
setIframeValue('cardexpiremonth', 'select', 5);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,18 @@ export default class PayonePaymentCreditCard extends Plugin {
_handleChangeSavedCard() {
const savedCardPan = document.getElementById('savedpseudocardpan');

if (savedCardPan.options[savedCardPan.selectedIndex].value) {
const selectedOption = savedCardPan.options[savedCardPan.selectedIndex];
if (selectedOption.value) {
[...document.getElementsByClassName('credit-card-input')].forEach(function(element) {
element.classList.add('hide')
});

if (!selectedOption.dataset.cardHolder?.length) {
document.getElementById('card-holder-input-wrapper').classList.remove('hide');
} else {
// TODO-card-holder-requirement: remove the next line (please see credit-card handler)
document.getElementById('creditCardHolder').value = selectedOption.dataset.cardHolder;
}
} else {
[...document.getElementsByClassName('credit-card-input')].forEach(function(element) {
element.classList.remove('hide');
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/snippet/de_DE/messages.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"creditCard": {
"newCard": "Eine neue Kreditkarte verwenden",
"cardHolder": "Karteninhaber",
"cardNumber": "Kartennummer",
"securityCode": "CVV",
"expiryDate": "Gültig bis",
Expand All @@ -32,6 +33,7 @@
"pageTitle": "Gespeicherte Kreditkarten",
"pageSubTitle": "Hier können Sie die gespeicherten Kreditkarten verwalten.",
"tableHeader": "Kartennummer",
"tableHeaderCardHolder": "Karteninhaber",
"deleteButton": "Karte löschen",
"noEntries": "Keine Kreditkarten vorhanden.",
"expired": "abgelaufen",
Expand Down
Loading

0 comments on commit dc3533d

Please sign in to comment.