77
88namespace Magento \Paypal \Model \Payflow ;
99
10+ use Magento \Framework \Exception \LocalizedException ;
11+ use Magento \Framework \Exception \State \InvalidTransitionException ;
1012use Magento \Payment \Helper \Formatter ;
1113use Magento \Payment \Model \InfoInterface ;
12- use Magento \Paypal \Model \Payflowpro ;
13- use Magento \Sales \Api \Data \OrderPaymentExtensionInterfaceFactory ;
14- use Magento \Sales \Model \Order \Payment ;
15- use Magento \Paypal \Model \Payflow \Service \Gateway ;
16- use Magento \Framework \Exception \LocalizedException ;
17- use Magento \Payment \Model \Method \TransparentInterface ;
1814use Magento \Payment \Model \Method \ConfigInterfaceFactory ;
19- use Magento \Framework \Exception \State \InvalidTransitionException ;
15+ use Magento \Payment \Model \Method \TransparentInterface ;
16+ use Magento \Payment \Model \MethodInterface ;
17+ use Magento \Paypal \Model \Payflow \Service \Gateway ;
2018use Magento \Paypal \Model \Payflow \Service \Response \Handler \HandlerInterface ;
2119use Magento \Paypal \Model \Payflow \Service \Response \Validator \ResponseValidator ;
20+ use Magento \Paypal \Model \Payflowpro ;
21+ use Magento \Sales \Api \Data \OrderPaymentExtensionInterfaceFactory ;
22+ use Magento \Sales \Model \Order \Payment ;
2223use Magento \Vault \Api \Data \PaymentTokenInterface ;
2324use Magento \Vault \Api \Data \PaymentTokenInterfaceFactory ;
2425
2526/**
26- * Payflow Pro payment gateway model
27+ * Payflow Pro payment gateway model (transparent redirect).
2728 *
2829 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2930 */
@@ -35,6 +36,16 @@ class Transparent extends Payflowpro implements TransparentInterface
3536
3637 const CC_VAULT_CODE = 'payflowpro_cc_vault ' ;
3738
39+ /**
40+ * Result code of account verification transaction request.
41+ */
42+ private const RESULT_CODE = 'result_code ' ;
43+
44+ /**
45+ * Fraud Management Filters config setting.
46+ */
47+ private const CONFIG_FMF = 'fmf ' ;
48+
3849 /**
3950 * @var string
4051 */
@@ -45,6 +56,13 @@ class Transparent extends Payflowpro implements TransparentInterface
4556 */
4657 protected $ _infoBlockType = \Magento \Paypal \Block \Payment \Info::class;
4758
59+ /**
60+ * Fetch transaction details availability option.
61+ *
62+ * @var bool
63+ */
64+ protected $ _canFetchTransactionInfo = false ;
65+
4866 /**
4967 * @var ResponseValidator
5068 */
@@ -165,6 +183,14 @@ public function validate()
165183 */
166184 public function authorize (InfoInterface $ payment , $ amount )
167185 {
186+ if ($ this ->isFraudDetected ($ payment )) {
187+ $ this ->markPaymentAsFraudulent ($ payment );
188+ return $ this ;
189+ }
190+
191+ $ zeroAmountAuthorizationId = $ this ->getZeroAmountAuthorizationId ($ payment );
192+ /** @var PaymentTokenInterface $vaultPaymentToken */
193+ $ vaultPaymentToken = $ payment ->getExtensionAttributes ()->getVaultPaymentToken ();
168194 /** @var Payment $payment */
169195 $ request = $ this ->buildBasicRequest ();
170196
@@ -177,9 +203,9 @@ public function authorize(InfoInterface $payment, $amount)
177203 $ payPalCart = $ this ->payPalCartFactory ->create (['salesModel ' => $ order ]);
178204 $ payPalCart ->getAmounts ();
179205
180- $ token = $ payment -> getAdditionalInformation ( self :: PNREF ) ;
206+ $ parentTransactionId = $ vaultPaymentToken ? $ vaultPaymentToken -> getGatewayToken () : $ zeroAmountAuthorizationId ;
181207 $ request ->setData ('trxtype ' , self ::TRXTYPE_AUTH_ONLY );
182- $ request ->setData ('origid ' , $ token );
208+ $ request ->setData ('origid ' , $ parentTransactionId );
183209 $ request ->setData ('amt ' , $ this ->formatPrice ($ amount ));
184210 $ request ->setData ('currency ' , $ order ->getBaseCurrencyCode ());
185211 $ request ->setData ('itemamt ' , $ this ->formatPrice ($ payPalCart ->getSubtotal ()));
@@ -200,10 +226,15 @@ public function authorize(InfoInterface $payment, $amount)
200226
201227 $ this ->setTransStatus ($ payment , $ response );
202228
203- $ this ->createPaymentToken ($ payment , $ token );
229+ if ($ vaultPaymentToken ) {
230+ $ payment ->setParentTransactionId ($ vaultPaymentToken ->getGatewayToken ());
231+ } else {
232+ $ this ->createPaymentToken ($ payment , $ zeroAmountAuthorizationId );
233+ }
204234
205235 $ payment ->unsAdditionalInformation (self ::CC_DETAILS );
206236 $ payment ->unsAdditionalInformation (self ::PNREF );
237+ $ payment ->unsAdditionalInformation (self ::RESULT_CODE );
207238
208239 return $ this ;
209240 }
@@ -291,14 +322,126 @@ private function getPaymentExtensionAttributes(Payment $payment)
291322 */
292323 public function capture (InfoInterface $ payment , $ amount )
293324 {
325+ if ($ this ->isFraudDetected ($ payment )) {
326+ $ this ->markPaymentAsFraudulent ($ payment );
327+ return $ this ;
328+ }
329+
294330 /** @var Payment $payment */
295- $ token = $ payment ->getAdditionalInformation (self ::PNREF );
331+ $ zeroAmountAuthorizationId = $ this ->getZeroAmountAuthorizationId ($ payment );
332+ /** @var PaymentTokenInterface $vaultPaymentToken */
333+ $ vaultPaymentToken = $ payment ->getExtensionAttributes ()->getVaultPaymentToken ();
334+ if ($ vaultPaymentToken && empty ($ zeroAmountAuthorizationId )) {
335+ $ payment ->setAdditionalInformation (self ::PNREF , $ vaultPaymentToken ->getGatewayToken ());
336+ if (!$ payment ->getParentTransactionId ()) {
337+ $ payment ->setParentTransactionId ($ vaultPaymentToken ->getGatewayToken ());
338+ }
339+ }
296340 parent ::capture ($ payment , $ amount );
297341
298- if ($ token && ! $ payment -> getAuthorizationTransaction () ) {
299- $ this ->createPaymentToken ($ payment , $ token );
342+ if ($ zeroAmountAuthorizationId && $ vaultPaymentToken === null ) {
343+ $ this ->createPaymentToken ($ payment , $ zeroAmountAuthorizationId );
300344 }
301345
302346 return $ this ;
303347 }
348+
349+ /**
350+ * Attempt to accept a pending payment.
351+ *
352+ * Order acquires a payment review state based on results of PayPal account verification transaction (zero-amount
353+ * authorization). For accepting a payment should be created PayPal reference transaction with a real order amount.
354+ * Fraud Protection Service filters do not screen reference transactions.
355+ *
356+ * @param InfoInterface $payment
357+ * @return bool
358+ * @throws InvalidTransitionException
359+ * @throws LocalizedException
360+ */
361+ public function acceptPayment (InfoInterface $ payment )
362+ {
363+ if ($ this ->getConfigPaymentAction () === MethodInterface::ACTION_AUTHORIZE_CAPTURE ) {
364+ $ invoices = iterator_to_array ($ payment ->getOrder ()->getInvoiceCollection ());
365+ $ invoice = count ($ invoices ) ? reset ($ invoices ) : null ;
366+ $ payment ->capture ($ invoice );
367+ } else {
368+ $ amount = $ payment ->getOrder ()->getBaseGrandTotal ();
369+ $ payment ->authorize (true , $ amount );
370+ }
371+
372+ return true ;
373+ }
374+
375+ /**
376+ * Deny a pending payment.
377+ *
378+ * Order acquires a payment review state based on results of PayPal account verification transaction (zero-amount
379+ * authorization). This transaction type cannot be voided, so we do not send any request to payment gateway.
380+ *
381+ * @param InfoInterface $payment
382+ * @return bool
383+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
384+ */
385+ public function denyPayment (InfoInterface $ payment )
386+ {
387+ return true ;
388+ }
389+
390+ /**
391+ * Marks payment as fraudulent.
392+ *
393+ * @param InfoInterface $payment
394+ * @throws \Exception
395+ */
396+ private function markPaymentAsFraudulent (InfoInterface $ payment ): void
397+ {
398+ $ zeroAmountAuthorizationId = $ this ->getZeroAmountAuthorizationId ($ payment );
399+ $ payment ->setTransactionId ($ zeroAmountAuthorizationId );
400+ $ payment ->setIsTransactionClosed (0 );
401+ $ payment ->setIsTransactionPending (true );
402+ $ payment ->setIsFraudDetected (true );
403+ $ this ->createPaymentToken ($ payment , $ zeroAmountAuthorizationId );
404+ $ fraudulentMsg = 'Order is suspended as an account verification transaction is suspected to be fraudulent. ' ;
405+ $ extensionAttributes = $ this ->getPaymentExtensionAttributes ($ payment );
406+ $ extensionAttributes ->setNotificationMessage ($ fraudulentMsg );
407+ $ payment ->unsAdditionalInformation (self ::CC_DETAILS );
408+ $ payment ->unsAdditionalInformation (self ::PNREF );
409+ $ payment ->unsAdditionalInformation (self ::RESULT_CODE );
410+ }
411+
412+ /**
413+ * Checks if fraud filters were triggered for the payment.
414+ *
415+ * For current PayPal PayflowPro transparent redirect integration
416+ * Fraud Protection Service filters screen only account verification
417+ * transaction (also known as zero dollar authorization).
418+ * Following reference transaction with real dollar amount will not be screened
419+ * by Fraud Protection Service.
420+ *
421+ * @param InfoInterface $payment
422+ * @return bool
423+ */
424+ private function isFraudDetected (InfoInterface $ payment ): bool
425+ {
426+ $ resultCode = $ payment ->getAdditionalInformation (self ::RESULT_CODE );
427+ $ isFmfEnabled = (bool )$ this ->getConfig ()->getValue (self ::CONFIG_FMF );
428+ return $ isFmfEnabled && $ this ->getZeroAmountAuthorizationId ($ payment ) && in_array (
429+ $ resultCode ,
430+ [self ::RESPONSE_CODE_DECLINED_BY_FILTER , self ::RESPONSE_CODE_FRAUDSERVICE_FILTER ]
431+ );
432+ }
433+
434+ /**
435+ * Returns zero dollar authorization transaction id.
436+ *
437+ * PNREF (transaction id) is available in payment additional information only right after
438+ * PayPal account verification transaction (also known as zero dollar authorization).
439+ *
440+ * @param InfoInterface $payment
441+ * @return string
442+ */
443+ private function getZeroAmountAuthorizationId (InfoInterface $ payment ): string
444+ {
445+ return (string )$ payment ->getAdditionalInformation (self ::PNREF );
446+ }
304447}
0 commit comments