diff --git a/.travis.yml b/.travis.yml index e7475ca..e7af9be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,21 @@ language: php php: - - 5.4 - - 5.3 + - 7.1 + - 7.3 + +matrix: + include: + - php: '7.1' + env: deps=low + +env: + global: + - deps=high before_script: - - composer install --dev \ No newline at end of file + - if [ "$deps" = "high" ]; then composer install --ignore-platform-reqs; fi; + - if [ "$deps" = "low" ]; then sed -i -e 's/>=3.4 <5.0/^3.4/g' composer.json && composer --ignore-platform-reqs update; fi + +script: + - vendor/bin/phpstan analyse -l 1 . + - export SYMFONY_DEPRECATIONS_HELPER=weak && vendor/bin/phpunit diff --git a/Client/Authentication/KeyedCredentialsAuthenticationStrategyInterface.php b/Client/Authentication/KeyedCredentialsAuthenticationStrategyInterface.php new file mode 100644 index 0000000..b8fd540 --- /dev/null +++ b/Client/Authentication/KeyedCredentialsAuthenticationStrategyInterface.php @@ -0,0 +1,14 @@ +curlOptions = array(); } - public function requestAddressVerify($email, $street, $postalCode) + public function requestAddressVerify($email, $street, $postalCode, $key = null) { return $this->sendApiRequest(array( 'METHOD' => 'AddressVerify', 'EMAIL' => $email, 'STREET' => $street, 'ZIP' => $postalCode, - )); + ), $key); } - public function requestBillOutstandingAmount($profileId, array $optionalParameters = array()) + public function requestBillOutstandingAmount($profileId, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'BillOutstandingAmount', 'PROFILEID' => $profileId, - ))); + )), $key); } - public function requestCreateRecurringPaymentsProfile($token) + public function requestCreateRecurringPaymentsProfile($token, $key = null) { return $this->sendApiRequest(array( 'METHOD' => 'CreateRecurringPaymentsProfile', 'TOKEN' => $token, - )); + ), $key); } - public function requestDoAuthorization($transactionId, $amount, array $optionalParameters = array()) + public function requestDoAuthorization($transactionId, $amount, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'DoAuthorization', 'TRANSACTIONID' => $transactionId, 'AMT' => $this->convertAmountToPaypalFormat($amount), - ))); + )), $key); } - public function requestDoCapture($authorizationId, $amount, $completeType, array $optionalParameters = array()) + public function requestDoReAuthorization($authorizationId, $amount, array $optionalParameters = array(), $key = null) + { + return $this->sendApiRequest(array_merge($optionalParameters, array( + 'METHOD' => 'DoReAuthorization', + 'AUTHORIZATIONID' => $authorizationId, + 'AMT' => $this->convertAmountToPaypalFormat($amount), + )), $key); + } + + public function requestDoCapture($authorizationId, $amount, $completeType, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'DoCapture', 'AUTHORIZATIONID' => $authorizationId, 'AMT' => $this->convertAmountToPaypalFormat($amount), 'COMPLETETYPE' => $completeType, - ))); + )), $key); } - public function requestDoDirectPayment($ipAddress, array $optionalParameters = array()) + public function requestDoDirectPayment($ipAddress, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'DoDirectPayment', 'IPADDRESS' => $ipAddress, - ))); + )), $key); } - public function requestDoExpressCheckoutPayment($token, $amount, $paymentAction, $payerId, array $optionalParameters = array()) + public function requestDoExpressCheckoutPayment($token, $amount, $paymentAction, $payerId, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'DoExpressCheckoutPayment', @@ -101,15 +111,15 @@ public function requestDoExpressCheckoutPayment($token, $amount, $paymentAction, 'PAYMENTREQUEST_0_AMT' => $this->convertAmountToPaypalFormat($amount), 'PAYMENTREQUEST_0_PAYMENTACTION' => $paymentAction, 'PAYERID' => $payerId, - ))); + )), $key); } - public function requestDoVoid($authorizationId, array $optionalParameters = array()) + public function requestDoVoid($authorizationId, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'DoVoid', 'AUTHORIZATIONID' => $authorizationId, - ))); + )), $key); } /** @@ -122,43 +132,44 @@ public function requestDoVoid($authorizationId, array $optionalParameters = arra * @param string $returnUrl * @param string $cancelUrl * @param array $optionalParameters + * @param string $key * @return Response */ - public function requestSetExpressCheckout($amount, $returnUrl, $cancelUrl, array $optionalParameters = array()) + public function requestSetExpressCheckout($amount, $returnUrl, $cancelUrl, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'SetExpressCheckout', 'PAYMENTREQUEST_0_AMT' => $this->convertAmountToPaypalFormat($amount), 'RETURNURL' => $returnUrl, 'CANCELURL' => $cancelUrl, - ))); + )), $key); } - public function requestGetExpressCheckoutDetails($token) + public function requestGetExpressCheckoutDetails($token, $key = null) { return $this->sendApiRequest(array( 'METHOD' => 'GetExpressCheckoutDetails', 'TOKEN' => $token, - )); + ), $key); } - public function requestGetTransactionDetails($transactionId) + public function requestGetTransactionDetails($transactionId, $key = null) { return $this->sendApiRequest(array( 'METHOD' => 'GetTransactionDetails', 'TRANSACTIONID' => $transactionId, - )); + ), $key); } - public function requestRefundTransaction($transactionId, array $optionalParameters = array()) + public function requestRefundTransaction($transactionId, array $optionalParameters = array(), $key = null) { return $this->sendApiRequest(array_merge($optionalParameters, array( 'METHOD' => 'RefundTransaction', 'TRANSACTIONID' => $transactionId - ))); + )), $key); } - public function sendApiRequest(array $parameters) + public function sendApiRequest(array $parameters, $key = null) { // include some default parameters $parameters['VERSION'] = self::API_VERSION; @@ -169,7 +180,11 @@ public function sendApiRequest(array $parameters) 'POST', $parameters ); - $this->authenticationStrategy->authenticate($request); + if (null !== $key && $this->authenticationStrategy instanceof KeyedCredentialsAuthenticationStrategyInterface) { + $this->authenticationStrategy->authenticateWithKeyedCredentials($request, $key); + } else { + $this->authenticationStrategy->authenticate($request); + } $response = $this->request($request); if (200 !== $response->getStatus()) { @@ -236,6 +251,7 @@ public function request(Request $request) $curl = curl_init(); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); // Latest TLS(1.x) curl_setopt_array($curl, $this->curlOptions); curl_setopt($curl, CURLOPT_URL, $request->getUri()); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 8e8e4ca..1adc08c 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -36,6 +36,7 @@ public function getConfigTree() ->scalarNode('cancel_url')->defaultNull()->end() ->scalarNode('notify_url')->defaultNull()->end() ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() + ->booleanNode('recover_from_funding_failure')->defaultFalse()->end() ->end() ->end() ->buildTree(); diff --git a/DependencyInjection/JMSPaymentPaypalExtension.php b/DependencyInjection/JMSPaymentPaypalExtension.php index 55a2453..61ba6ca 100644 --- a/DependencyInjection/JMSPaymentPaypalExtension.php +++ b/DependencyInjection/JMSPaymentPaypalExtension.php @@ -42,5 +42,10 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('payment.paypal.express_checkout.cancel_url', $config['cancel_url']); $container->setParameter('payment.paypal.express_checkout.notify_url', $config['notify_url']); $container->setParameter('payment.paypal.debug', $config['debug']); + if ($config['recover_from_funding_failure']) { + $plugin = $container->getDefinition('payment.plugin.paypal_express_checkout'); + $plugin->addMethodCall('setRedirectDueToFundingError', [$config['recover_from_funding_failure']]); + } + } } diff --git a/Form/ExpressCheckoutType.php b/Form/ExpressCheckoutType.php index 9eb981e..db5930e 100755 --- a/Form/ExpressCheckoutType.php +++ b/Form/ExpressCheckoutType.php @@ -16,8 +16,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) { } - public function getName() + public function getBlockPrefix() { return 'paypal_express_checkout'; } -} \ No newline at end of file +} diff --git a/Plugin/ExpressCheckoutPlugin.php b/Plugin/ExpressCheckoutPlugin.php index 692914f..896fbbe 100644 --- a/Plugin/ExpressCheckoutPlugin.php +++ b/Plugin/ExpressCheckoutPlugin.php @@ -32,6 +32,10 @@ class ExpressCheckoutPlugin extends AbstractPlugin { + + const ERROR_CODE_HONOR_WINDOW = 10617; + const ERROR_CODE_FUNDING_FAILURE = 10486; + /** * @var string */ @@ -53,10 +57,22 @@ class ExpressCheckoutPlugin extends AbstractPlugin protected $client; /** - * @param string $returnUrl - * @param string $cancelUrl - * @param \JMS\Payment\PaypalBundle\Client\Client $client - * @param string $notifyUrl + * A function that, when given a FinancialTransactionInterface object, provides a credentials key to use. + * + * @var callable + */ + private $credentialsKeyResolver; + + /** + * @var boolean + */ + private $redirectDueToFundingError; + + /** + * @param bool $returnUrl + * @param $cancelUrl + * @param Client $client + * @param null $notifyUrl */ public function __construct($returnUrl, $cancelUrl, Client $client, $notifyUrl = null) { @@ -64,66 +80,180 @@ public function __construct($returnUrl, $cancelUrl, Client $client, $notifyUrl = $this->returnUrl = $returnUrl; $this->cancelUrl = $cancelUrl; $this->notifyUrl = $notifyUrl; + $this->credentialsKeyResolver = function () { + return null; + }; + $this->redirectDueToFundingError = false; + + parent::__construct(false); } + /** + * @param FinancialTransactionInterface $transaction + * @param bool $retry + * @throws ActionRequiredException + * @throws FinancialException + * @throws PaymentPendingException + */ public function approve(FinancialTransactionInterface $transaction, $retry) { + if ($transaction->getExtendedData()->has('ipn_decision')) { + $this->updateIpnTransaction($transaction); + return; + } + $this->createCheckoutBillingAgreement($transaction, 'Authorization'); } + /** + * @param FinancialTransactionInterface $transaction + * @param bool $retry + * @throws ActionRequiredException + * @throws FinancialException + * @throws PaymentPendingException + */ public function approveAndDeposit(FinancialTransactionInterface $transaction, $retry) { + if ($transaction->getExtendedData()->has('ipn_decision')) { + $this->updateIpnTransaction($transaction); + return; + } + $this->createCheckoutBillingAgreement($transaction, 'Sale'); } + /** + * @param FinancialTransactionInterface $transaction + * @throws FinancialException + */ + public function updateIpnTransaction(FinancialTransactionInterface $transaction) + { + $data = $transaction->getExtendedData(); + $decision = $data->get('ipn_decision'); + + if ($decision === 'Completed') { + $transaction->setProcessedAmount($transaction->getRequestedAmount()); + $transaction->setResponseCode(PluginInterface::RESPONSE_CODE_SUCCESS); + $transaction->setReasonCode(PluginInterface::REASON_CODE_SUCCESS); + + return; + } else { + $ex = new FinancialException('PaymentStatus is not completed: Denied'); + $ex->setFinancialTransaction($transaction); + $transaction->setResponseCode($decision); + $transaction->setReasonCode($decision); + + throw $ex; + } + } + public function credit(FinancialTransactionInterface $transaction, $retry) { $data = $transaction->getExtendedData(); $approveTransaction = $transaction->getCredit()->getPayment()->getApproveTransaction(); - $parameters = array(); + $parameters = []; if (Number::compare($transaction->getRequestedAmount(), $approveTransaction->getProcessedAmount()) !== 0) { $parameters['REFUNDTYPE'] = 'Partial'; $parameters['AMT'] = $this->client->convertAmountToPaypalFormat($transaction->getRequestedAmount()); $parameters['CURRENCYCODE'] = $transaction->getCredit()->getPaymentInstruction()->getCurrency(); } - $response = $this->client->requestRefundTransaction($data->get('authorization_id'), $parameters); + //pull the appropriate transaction id for the refund request depending on how the capture was originally made + if ($approveTransaction->getTransactionType() === FinancialTransactionInterface::TRANSACTION_TYPE_APPROVE_AND_DEPOSIT) { + $transactionId = $approveTransaction->getReferenceNumber(); + } else { + $depositTransaction = $transaction->getCredit()->getPayment()->getDepositTransactions()->first(); + $transactionId = $depositTransaction->getReferenceNumber(); + } + $response = $this->client->requestRefundTransaction($transactionId, $parameters, $this->getCredentialsKeyForTransaction($transaction)); + $this->saveResponseDetails($data, $response); $this->throwUnlessSuccessResponse($response, $transaction); $transaction->setReferenceNumber($response->body->get('REFUNDTRANSACTIONID')); - $transaction->setProcessedAmount($response->body->get('NETREFUNDAMT')); + $transaction->setProcessedAmount($response->body->get('GROSSREFUNDAMT')); $transaction->setResponseCode(PluginInterface::RESPONSE_CODE_SUCCESS); } + public function reApprove(FinancialTransactionInterface $transaction) + { + $originalAuthorizationId = $transaction->getPayment()->getApproveTransaction()->getReferenceNumber(); + $response = $this->client->requestDoReauthorization($originalAuthorizationId, $transaction->getRequestedAmount(), [ + 'CURRENCYCODE' => $transaction->getPayment()->getPaymentInstruction()->getCurrency() + ]); + $credentialsKey = $this->getCredentialsKeyForTransaction($transaction); + if ($response->body->get('ACK') == 'Success') { + //GEt the new AuthorizationId and update the transaction + $extendedData = $transaction->getExtendedData(); + //save the original_authorization_id for reference. + $extendedData->set('original_authorization_id', $originalAuthorizationId); + $newAuthorizationId = $response->body->get('AUTHORIZATIONID'); + $transaction->setReferenceNumber($newAuthorizationId); + $transaction->setExtendedData($extendedData); + $details = $this->client->requestGetTransactionDetails($newAuthorizationId, $credentialsKey); + $this->throwUnlessSuccessResponse($details, $transaction); + + switch ($details->body->get('PAYMENTSTATUS')) { + case 'Completed': + $transaction->setProcessedAmount($transaction->getRequestedAmount()); + $transaction->setResponseCode(PluginInterface::RESPONSE_CODE_SUCCESS); + $transaction->setReasonCode(PluginInterface::REASON_CODE_SUCCESS); + case 'Pending': + //This exception should be trow just if the reason of the 'pending state' is different to 'authorization state' + if ($response->body->get('PENDINGREASON') != 'authorization') { + throw new PaymentPendingException(sprintf('Payment is still pending: %s', $response->body->get('PENDINGREASON'))); + } + break; + default: + $ex = new FinancialException(sprintf('PaymentStatus is not completed: %s', $response->body->get('PAYMENTSTATUS'))); + $ex->setFinancialTransaction($transaction); + $transaction->setResponseCode('Failed'); + $transaction->setReasonCode($response->body->get('PAYMENTSTATUS')); + + throw $ex; + } + + } else { + $this->throwUnlessSuccessResponse($response, $transaction); + } + } + public function deposit(FinancialTransactionInterface $transaction, $retry) { $data = $transaction->getExtendedData(); $authorizationId = $transaction->getPayment()->getApproveTransaction()->getReferenceNumber(); + + //always 'Complete' as we are only capturing once, thus we always indicate that the authorisation is closed + $completeType = 'Complete'; - if (Number::compare($transaction->getPayment()->getApprovedAmount(), $transaction->getRequestedAmount()) === 0) { - $completeType = 'Complete'; - } - else { - $completeType = 'NotComplete'; - } + $credentialsKey = $this->getCredentialsKeyForTransaction($transaction); $response = $this->client->requestDoCapture($authorizationId, $transaction->getRequestedAmount(), $completeType, array( 'CURRENCYCODE' => $transaction->getPayment()->getPaymentInstruction()->getCurrency(), - )); + ), $credentialsKey); + //set reference to that of the deposit transaction ID, this can then be used for credit's later + $transaction->setReferenceNumber($response->body->get('TRANSACTIONID')); + + $this->saveResponseDetails($data, $response); $this->throwUnlessSuccessResponse($response, $transaction); - $details = $this->client->requestGetTransactionDetails($authorizationId); + $captureAmt = ($response->body->get('AMT')); + + $details = $this->client->requestGetTransactionDetails($authorizationId, $credentialsKey); $this->throwUnlessSuccessResponse($details, $transaction); - switch ($details->body->get('PAYMENTSTATUS')) { + + switch ($response->body->get('PAYMENTSTATUS')) { case 'Completed': break; case 'Pending': - throw new PaymentPendingException('Payment is still pending: '.$response->body->get('PENDINGREASON')); - + //This exception should be trow just if the reason of the 'pending state' is different to 'authorization state' + if ($response->body->get('PENDINGREASON')!='authorization') { + throw new PaymentPendingException('Payment is still pending: '.$response->body->get('PENDINGREASON')); + } + break; default: $ex = new FinancialException('PaymentStatus is not completed: '.$response->body->get('PAYMENTSTATUS')); $ex->setFinancialTransaction($transaction); @@ -133,8 +263,7 @@ public function deposit(FinancialTransactionInterface $transaction, $retry) throw $ex; } - $transaction->setReferenceNumber($authorizationId); - $transaction->setProcessedAmount($details->body->get('AMT')); + $transaction->setProcessedAmount($captureAmt); $transaction->setResponseCode(PluginInterface::RESPONSE_CODE_SUCCESS); $transaction->setReasonCode(PluginInterface::REASON_CODE_SUCCESS); } @@ -143,9 +272,10 @@ public function reverseApproval(FinancialTransactionInterface $transaction, $ret { $data = $transaction->getExtendedData(); - $response = $this->client->requestDoVoid($data->get('authorization_id')); + $response = $this->client->requestDoVoid($data->get('authorization_id'), [], $this->getCredentialsKeyForTransaction($transaction)); $this->throwUnlessSuccessResponse($response, $transaction); + $transaction->setProcessedAmount($transaction->getRequestedAmount()); $transaction->setResponseCode(PluginInterface::RESPONSE_CODE_SUCCESS); } @@ -159,18 +289,31 @@ public function isIndependentCreditSupported() return false; } + public function setCredentialsKeyResolver(callable $resolver) + { + $this->credentialsKeyResolver = $resolver; + } + + public function setRedirectDueToFundingError($redirect) + { + $this->redirectDueToFundingError = $redirect; + } + protected function createCheckoutBillingAgreement(FinancialTransactionInterface $transaction, $paymentAction) { $data = $transaction->getExtendedData(); - $token = $this->obtainExpressCheckoutToken($transaction, $paymentAction); - - $details = $this->client->requestGetExpressCheckoutDetails($token); + $credentialsKey = $this->getCredentialsKeyForTransaction($transaction); + $details = $this->client->requestGetExpressCheckoutDetails($token, $credentialsKey); + $this->saveResponseDetails($data, $details); $this->throwUnlessSuccessResponse($details, $transaction); // verify checkout status switch ($details->body->get('CHECKOUTSTATUS')) { case 'PaymentActionFailed': + if ($this->redirectDueToFundingError && $details->body->get('ACK') == 'Success') { + break; + } $ex = new FinancialException('PaymentAction failed.'); $transaction->setResponseCode('Failed'); $transaction->setReasonCode('PaymentActionFailed'); @@ -195,9 +338,9 @@ protected function createCheckoutBillingAgreement(FinancialTransactionInterface // complete the transaction $data->set('paypal_payer_id', $details->body->get('PAYERID')); - $optionalParameters = array( + $optionalParameters = [ 'PAYMENTREQUEST_0_CURRENCYCODE' => $transaction->getPayment()->getPaymentInstruction()->getCurrency(), - ); + ]; if (null !== $notifyUrl = $this->getNotifyUrl($data)) { $optionalParameters['PAYMENTREQUEST_0_NOTIFYURL'] = $notifyUrl; @@ -208,8 +351,12 @@ protected function createCheckoutBillingAgreement(FinancialTransactionInterface $transaction->getRequestedAmount(), $paymentAction, $details->body->get('PAYERID'), - $optionalParameters + $optionalParameters, + $credentialsKey ); + + $this->saveResponseDetails($data, $response); + $this->checkFundingErrorResponse($response, $transaction, $token); $this->throwUnlessSuccessResponse($response, $transaction); switch($response->body->get('PAYMENTINFO_0_PAYMENTSTATUS')) { @@ -217,16 +364,25 @@ protected function createCheckoutBillingAgreement(FinancialTransactionInterface break; case 'Pending': - $transaction->setReferenceNumber($response->body->get('PAYMENTINFO_0_TRANSACTIONID')); - - throw new PaymentPendingException('Payment is still pending: '.$response->body->get('PAYMENTINFO_0_PENDINGREASON')); - + //This exception should be trow just if the reason of the 'pending state' is different to 'authorization state' + if ($response->body->get('PAYMENTINFO_0_PENDINGREASON') != 'authorization') { + $transaction->setReferenceNumber($response->body->get('PAYMENTINFO_0_TRANSACTIONID')); + throw new PaymentPendingException(sprintf('Payment is still pending: %s', $response->body->get('PAYMENTINFO_0_PENDINGREASON'))); + } + break; default: - $ex = new FinancialException('PaymentStatus is not completed: '.$response->body->get('PAYMENTINFO_0_PAYMENTSTATUS')); + $ex = new FinancialException(sprintf('PaymentStatus is not completed: %s', $response->body->get('PAYMENTINFO_0_PAYMENTSTATUS'))); $ex->setFinancialTransaction($transaction); $transaction->setResponseCode('Failed'); $transaction->setReasonCode($response->body->get('PAYMENTINFO_0_PAYMENTSTATUS')); + //Set attention required on payment or credit + if (null !== $transaction->getPayment()) { + $transaction->getPayment()->setAttentionRequired(true); + } elseif (null !== $transaction->getCredit()) { + $transaction->getCredit()->setAttentionRequired(true); + } + throw $ex; } @@ -251,16 +407,20 @@ protected function obtainExpressCheckoutToken(FinancialTransactionInterface $tra return $data->get('express_checkout_token'); } - $opts = $data->has('checkout_params') ? $data->get('checkout_params') : array(); + $opts = $data->has('checkout_params') ? $data->get('checkout_params') : []; $opts['PAYMENTREQUEST_0_PAYMENTACTION'] = $paymentAction; $opts['PAYMENTREQUEST_0_CURRENCYCODE'] = $transaction->getPayment()->getPaymentInstruction()->getCurrency(); + $credentialsKey = $this->getCredentialsKeyForTransaction($transaction); + $response = $this->client->requestSetExpressCheckout( $transaction->getRequestedAmount(), $this->getReturnUrl($data), $this->getCancelUrl($data), - $opts + $opts, + $credentialsKey ); + $this->saveResponseDetails($data, $response); $this->throwUnlessSuccessResponse($response, $transaction); $data->set('express_checkout_token', $response->body->get('TOKEN')); @@ -295,6 +455,10 @@ protected function throwUnlessSuccessResponse(Response $response, FinancialTrans throw $ex; } + /** + * @param ExtendedDataInterface $data + * @return string + */ protected function getReturnUrl(ExtendedDataInterface $data) { if ($data->has('return_url')) { @@ -307,6 +471,10 @@ protected function getReturnUrl(ExtendedDataInterface $data) throw new \RuntimeException('You must configure a return url.'); } + /** + * @param ExtendedDataInterface $data + * @return string + */ protected function getCancelUrl(ExtendedDataInterface $data) { if ($data->has('cancel_url')) { @@ -319,6 +487,10 @@ protected function getCancelUrl(ExtendedDataInterface $data) throw new \RuntimeException('You must configure a cancel url.'); } + /** + * @param ExtendedDataInterface $data + * @return null|string + */ protected function getNotifyUrl(ExtendedDataInterface $data) { if ($data->has('notify_url')) { @@ -328,4 +500,33 @@ protected function getNotifyUrl(ExtendedDataInterface $data) return $this->notifyUrl; } } + + /** + * @param Response $response + * @param FinancialTransactionInterface $transaction + * @param String $token + * @throws ActionRequiredException + */ + protected function checkFundingErrorResponse(Response $response, FinancialTransactionInterface $transaction, $token) + { + if ($this->redirectDueToFundingError && $response->body->get('L_ERRORCODE0') == self::ERROR_CODE_FUNDING_FAILURE) { + $actionRequest = new ActionRequiredException('User has not funding available and need to select a new payment option.'); + $actionRequest->setFinancialTransaction($transaction); + $actionRequest->setAction(new VisitUrl($this->client->getAuthenticateExpressCheckoutTokenUrl($token))); + + throw $actionRequest; + } + } + + private function saveResponseDetails(ExtendedDataInterface $data, Response $details) + { + foreach ($details->body->all() as $key => $value) { + $data->set($key, $value); + } + } + + private function getCredentialsKeyForTransaction(FinancialTransactionInterface $transaction) + { + return call_user_func($this->credentialsKeyResolver, $transaction); + } } diff --git a/composer.json b/composer.json index eb9a53c..feb0e2d 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "jms/payment-paypal-bundle", + "name": "markup/jms-payment-paypal-bundle", "type": "symfony-bundle", - "description": "Payment Bundle providing access to the PayPal API", + "description": "Payment Bundle providing access to the PayPal API, based on the JMS bundle", "keywords": ["payment", "paypal"], "homepage": "http://jmsyst.com/bundles/JMSPaymentPaypalBundle", "license": "Apache2", @@ -12,12 +12,20 @@ } ], "require": { - "php": ">=5.3.2", - "symfony/framework-bundle": "~2.0", - "jms/payment-core-bundle": "~1.0" + "php": ">=7.1", + "symfony/browser-kit": ">=3.4 <5.0", + "symfony/config": ">=3.4 <5.0", + "symfony/dependency-injection": ">=3.4 <5.0", + "symfony/form": ">=3.4 <5.0", + "symfony/http-foundation": ">=3.4 <5.0", + "symfony/http-kernel": ">=3.4 <5.0", + "jms/payment-core-bundle": "~1.0|~2.0", + "doctrine/collections": "^1" }, "require-dev": { - "symfony/browser-kit": "*" + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "~4.8|~5.4", + "symfony/phpunit-bridge": "~2.7" }, "autoload": { "psr-0": { "JMS\\Payment\\PaypalBundle": "" } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0e79d8f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + excludes_analyse: + - %currentWorkingDirectory%/DependencyInjection/Configuration.php + - %currentWorkingDirectory%/Tests/* + - %currentWorkingDirectory%/vendor/* + - %currentWorkingDirectory%/bin/* + reportUnmatchedIgnoredErrors: true