Skip to content

Commit

Permalink
card on file standard merchant flow
Browse files Browse the repository at this point in the history
  • Loading branch information
konradkozaczenko committed May 21, 2024
1 parent 4f82750 commit fa00de4
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 24 deletions.
37 changes: 37 additions & 0 deletions src/Action/CaptureAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Answear\Payum\PayU\Action;

use Answear\Payum\Model\Payment;
use Answear\Payum\PayU\Enum\CardOnFileEnum;
use Answear\Payum\PayU\Enum\PayMethodType;
use Answear\Payum\PayU\Enum\RecurringEnum;
use Answear\Payum\PayU\Exception\PayUException;
Expand All @@ -17,6 +18,7 @@
use Answear\Payum\PayU\ValueObject\Request\OrderRequest;
use Answear\Payum\PayU\ValueObject\Response\OrderCreated\StatusCode;
use Answear\Payum\PayU\ValueObject\Response\OrderCreatedResponse;
use Answear\Payum\PayU\ValueObject\Response\OrderTransactions\ByCreditCard;
use Payum\Core\Action\ActionInterface;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\GatewayAwareInterface;
Expand Down Expand Up @@ -59,10 +61,15 @@ public function execute($request): void

$configKey = PaymentHelper::getConfigKey($model, $firstModel);
$orderRequest = $this->prepareOrderRequest($request, $token, $model);

if (RecurringEnum::Standard === $model->recurring()) {
$this->setRecurringStandardPayment($orderRequest, $model, $configKey);
}

if (CardOnFileEnum::StandardMerchant === $model->cardOnFile()) {
$this->setCardOnFileStandardMerchantPayment($orderRequest, $model, $configKey);
}

$orderCreatedResponse = $this->orderRequestService->create($orderRequest, $configKey);
$model->setPayUResponse($orderCreatedResponse);
if (StatusCode::Success === $orderCreatedResponse->status->statusCode) {
Expand Down Expand Up @@ -157,6 +164,36 @@ private function prepareOrderRequest(Capture $request, TokenInterface $token, Mo
);
}

private function setCardOnFileStandardMerchantPayment(OrderRequest $orderRequest, Model $model, ?string $configKey): void
{
$payMethods = $this->payMethodsRequestService->retrieveForUser($model->clientEmail(), $model->clientId(), $configKey);
if (empty($payMethods->cardTokens)) {
throw new \InvalidArgumentException('Cannot make this payment. Token for user does not exist.');
}

$transactions = $this->orderRequestService->retrieveTransactions($model->orderId(), $configKey);
$cardToken = null;
foreach ($transactions as $transaction) {
if (!$transaction instanceof ByCreditCard) {
continue;
}

$cardToken = $this->findPreferredToken($payMethods->cardTokens, $transaction->cardData->cardNumberMasked);
if (null !== $cardToken) {
break;
}
}

if (null === $cardToken) {
throw new \InvalidArgumentException('Cannot make this payment. Token for user does not exist.');
}

$orderRequest->setCardOnFile(
$model->cardOnFile() ?? CardOnFileEnum::StandardMerchant,
new PayMethod(PayMethodType::CardToken, $cardToken['value'])
);
}

private function setRecurringStandardPayment(OrderRequest $orderRequest, Model $model, ?string $configKey): void
{
$payMethods = $this->payMethodsRequestService->retrieveForUser($model->clientEmail(), $model->clientId(), $configKey);
Expand Down
6 changes: 6 additions & 0 deletions src/ValueObject/Request/OrderRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,10 @@ public function setRequiring(?RecurringEnum $recurring, PayMethod $payMethod): v
$this->recurring = $recurring?->value;
$this->payMethod = $payMethod;
}

public function setCardOnFile(CardOnFileEnum $cardOnFile, PayMethod $payMethod): void
{
$this->cardOnFile = $cardOnFile;
$this->payMethod = $payMethod;
}
}
6 changes: 4 additions & 2 deletions src/ValueObject/Response/OrderTransactions/ByCreditCard.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class ByCreditCard implements OrderRetrieveTransactionsResponseInterface
public function __construct(
public readonly PayMethod $payMethod,
public readonly string $paymentFlow,
public readonly ?array $card,
public readonly CardData $cardData,
public readonly array $cardInstallmentProposal,
) {
}

Expand All @@ -25,7 +26,8 @@ public static function fromResponse(array $response): self
return new self(
PayMethod::fromResponse($response['payMethod']),
$response['paymentFlow'],
$response['card'] ?? null,
CardData::fromResponse($response['card']['cardData']),
$response['cardInstallmentProposal'],
);
}
}
42 changes: 42 additions & 0 deletions src/ValueObject/Response/OrderTransactions/CardData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Answear\Payum\PayU\ValueObject\Response\OrderTransactions;

class CardData
{
public function __construct(
public readonly string $cardNumberMasked,
public readonly string $cardScheme,
public readonly string $cardProfile,
public readonly string $cardClassification,
public readonly string $cardResponseCode,
public readonly string $cardResponseCodeDesc,
public readonly string $cardEciCode,
public readonly string $card3DsStatus,
public readonly string $card3DsFrictionlessIndicator,
public readonly string $card3DsStatusDescription,
public readonly string $cardBinCountry,
public readonly string $firstTransactionId,
) {
}

public static function fromResponse(array $response): self
{
return new self(
$response['cardNumberMasked'] ?? '',
$response['cardScheme'] ?? '',
$response['cardProfile'] ?? '',
$response['cardClassification'] ?? '',
$response['cardResponseCode'] ?? '',
$response['cardResponseCodeDesc'] ?? '',
$response['cardEciCode'] ?? '',
$response['card3DsStatus'] ?? '',
$response['card3DsFrictionlessIndicator'] ?? '',
$response['card3DsStatusDescription'] ?? '',
$response['cardBinCountry'] ?? '',
$response['firstTransactionId'] ?? '',
);
}
}
48 changes: 46 additions & 2 deletions tests/Integration/Action/CaptureActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Answear\Payum\PayU\ValueObject\Response\OrderCreated\OrderCreatedStatus;
use Answear\Payum\PayU\ValueObject\Response\OrderCreated\StatusCode;
use Answear\Payum\PayU\ValueObject\Response\OrderCreatedResponse;
use Answear\Payum\PayU\ValueObject\Response\PayMethodsResponse;
use Payum\Core\Gateway;
use Payum\Core\Model\Payment;
use Payum\Core\Model\PaymentInterface;
Expand Down Expand Up @@ -192,10 +193,53 @@ public function captureWithOrderIdFailsTest(): void
$captureAction->execute($capture);
}

/**
* @test
*/
public function captureWithCardOnFile(): void
{
$payMethodRequestService = $this->createMock(PayMethodsRequestService::class);
$payMethodRequestService->method('retrieveForUser')
->willReturn(
PayMethodsResponse::fromResponse(
FileTestUtil::decodeJsonFromFile(__DIR__ . '/data/retrievePayMethodsForUserResponse.json')
)
);

$captureAction = $this->getCaptureAction(
expectedCreateRequest: FileTestUtil::decodeJsonFromFile(__DIR__ . '/data/expectedOrderRequestWithCardOnFile.json'),
details: FileTestUtil::decodeJsonFromFile(__DIR__ . '/data/detailsWithCardOnFileStandardMerchant.json'),
payMethodsRequestService: $payMethodRequestService,
);

$payment = new \Answear\Payum\PayU\Tests\Payment();
$payment->setDetails(FileTestUtil::decodeJsonFromFile(__DIR__ . '/data/detailsWithCardOnFileStandardMerchant.json'));

$payMethod = new PayMethod(
PayMethodType::Pbl,
'some value',
'some authorisation code'
);

$captureToken = new Token();
$capture = new \Answear\Payum\Action\Request\Capture($captureToken, $payment, $payMethod);

$redirected = false;
try {
$captureAction->execute($capture);
} catch (HttpRedirect $httpRedirect) {
$redirected = true;
self::assertSame('http://redirect-after-create-payment.url', $httpRedirect->getUrl());
}

self::assertTrue($redirected);
}

private function getCaptureAction(
?OrderCreatedResponse $response = null,
?array $details = null,
?array $expectedCreateRequest = null
?array $expectedCreateRequest = null,
?MockObject $payMethodsRequestService = null,
): CaptureAction {
$response = $response ?? new OrderCreatedResponse(
new OrderCreatedStatus(
Expand Down Expand Up @@ -226,7 +270,7 @@ static function (OrderRequest $createRequest) use ($expectedCreateRequest) {

$captureAction = new CaptureAction(
$orderRequestService,
$this->createMock(PayMethodsRequestService::class)
$payMethodsRequestService ?? $this->createMock(PayMethodsRequestService::class)
);

$notifyToken = $this->createMock(TokenInterface::class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"totalAmount": 95500,
"firstName": "Testy",
"lastName": "Mjzykdwmh",
"description": "Platnost za objedn\u00e1vku \u010d.: 221214-0026UJ-CZ",
"currencyCode": "CZK",
"language": "cs",
"validityTime": 259200,
"buyer": {
"email": "[email protected]",
"firstName": "Testy",
"lastName": "Mjzykdwmh",
"phone": "+420733999019",
"language": "cs"
},
"extOrderId": "221214-0026UJ-CZ",
"clientEmail": "[email protected]",
"clientId": "124077",
"customerIp": "10.0.13.152",
"creditCardMaskedNumber": null,
"status": "PENDING",
"payUResponse": {
"status": {
"statusCode": "SUCCESS"
},
"redirectUri": "https:\/\/merch-prod.snd.payu.com\/pay\/?orderId=3MRW8ST2Z6221214GUEST000P01&token=eyJhbGciOiJIUzI1NiJ9.eyJvcmRlcklkIjoiM01SVzhTVDJaNjIyMTIxNEdVRVNUMDAwUDAxIiwicG9zSWQiOiI5eWJZVWFZOSIsImF1dGhvcml0aWVzIjpbIlJPTEVfQ0xJRU5UIl0sInBheWVyRW1haWwiOiJ0ZXN0eS5hdXRvbWF0eWN6bmUrZGVjMTQyMDIyMjMyODUzNDgwMDIzQGFuc3dlYXIuY29tIiwiZXhwIjoxNjcxMzE2Mjk1LCJpc3MiOiJQQVlVIiwiYXVkIjoiYXBpLWdhdGV3YXkiLCJzdWIiOiJQYXlVIHN1YmplY3QiLCJqdGkiOiI3NmYyOGZkMi1jOWIxLTRiYzAtOTM5Zi0xNjQ5NjY0ZWNlZDMifQ.NpmBZw0vQP7WEWQEd-ZhXoyg8oo_eKy8gEyfAjri21g",
"orderId": "3MRW8ST2Z6221214GUEST000P01",
"extOrderId": "221214-0026UJ-CZ"
},
"orderId": "3MRW8ST2Z6221214GUEST000P01",
"cardOnFile": "STANDARD_MERCHANT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"extOrderId": "221214-0026UJ-CZ",
"notifyUrl": "http:\/\/notify.url",
"customerIp": "10.0.13.152",
"merchantPosId": "posId",
"validityTime": 259200,
"description": "Platnost za objedn\u00e1vku \u010d.: 221214-0026UJ-CZ",
"additionalDescription": null,
"visibleDescription": null,
"statementDescription": null,
"currencyCode": "CZK",
"totalAmount": 95500,
"continueUrl": null,
"buyer": {
"email": "[email protected]",
"firstName": "Testy",
"lastName": "Mjzykdwmh",
"phone": "+420733999019",
"customerId": null,
"extCustomerId": null,
"nin": null,
"language": "cs",
"delivery": null
},
"products": [
{
"name": "Platnost za objedn\u00e1vku \u010d.: 221214-0026UJ-CZ",
"unitPrice": 95500,
"quantity": 1,
"virtual": null,
"listingDate": null
}
],
"payMethods": null,
"cardOnFile": "STANDARD_MERCHANT",
"recurring": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"cardTokens": [
{
"status": "ACTIVE",
"cardNumberMasked": "111122*******444"
},
{
"status": "EXPIRED",
"cardNumberMasked": "666677*******999"
}
],
"status": {
"statusCode": "SUCCESS"
}
}
39 changes: 22 additions & 17 deletions tests/Integration/Request/OrderRetrieveTransactionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Answear\Payum\PayU\ValueObject\PayMethod;
use Answear\Payum\PayU\ValueObject\Response\OrderTransactions\ByCreditCard;
use Answear\Payum\PayU\ValueObject\Response\OrderTransactions\ByPBL;
use Answear\Payum\PayU\ValueObject\Response\OrderTransactions\CardData;
use GuzzleHttp\Psr7\Response;
use Psr\Log\NullLogger;

Expand All @@ -33,26 +34,30 @@ public function retrieveByCardTest(): void
$transaction->getPayMethod()
);
self::assertSame('FIRST_ONE_CLICK_CARD', $transaction->paymentFlow);

$expectedCardData = CardData::fromResponse(
[
'cardNumberMasked' => '543402******4014',
'cardScheme' => 'MC',
'cardProfile' => 'CONSUMER',
'cardClassification' => 'DEBIT',
'cardResponseCode' => '000',
'cardResponseCodeDesc' => '000 - OK',
'cardEciCode' => '2',
'card3DsStatus' => 'Y',
'card3DsStatusDescription' => 'MessageVersion=2.1.0,browser flow,3DS method not available,dynamic authentication,no cancel indicator,no status reason',
'cardBinCountry' => 'PL',
'firstTransactionId' => 'MCC0111LL1121',
]
);

self::assertEquals($expectedCardData, $transaction->cardData);

self::assertSame(
[
'cardData' => [
'cardNumberMasked' => '543402******4014',
'cardScheme' => 'MC',
'cardProfile' => 'CONSUMER',
'cardClassification' => 'DEBIT',
'cardResponseCode' => '000',
'cardResponseCodeDesc' => '000 - OK',
'cardEciCode' => '2',
'card3DsStatus' => 'Y',
'card3DsStatusDescription' => 'MessageVersion=2.1.0,browser flow,3DS method not available,dynamic authentication,no cancel indicator,no status reason',
'cardBinCountry' => 'PL',
'firstTransactionId' => 'MCC0111LL1121',
],
'cardInstallmentProposal' => [
'proposalId' => '5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed',
],
'proposalId' => '5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed',
],
$transaction->card
$transaction->cardInstallmentProposal
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
"card3DsStatusDescription": "MessageVersion=2.1.0,browser flow,3DS method not available,dynamic authentication,no cancel indicator,no status reason",
"cardBinCountry": "PL",
"firstTransactionId": "MCC0111LL1121"
},
"cardInstallmentProposal": {
"proposalId": "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
}
},
"cardInstallmentProposal": {
"proposalId": "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
}
}
]
Expand Down

0 comments on commit fa00de4

Please sign in to comment.