Completed
Push — master ( e6dbd1...1fc506 )
by Jeroen De
95:08 queued 30:04
created

requestIsForPaymentCompletion()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 3
nop 1
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\App\RouteHandlers;
6
7
use Psr\Log\LogLevel;
8
use Symfony\Component\HttpFoundation\ParameterBag;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
11
use WMDE\Euro\Euro;
12
use WMDE\Fundraising\Frontend\PaymentContext\ResponseModel\PaypalNotificationResponse;
13
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
14
use WMDE\Fundraising\Frontend\Infrastructure\PayPalPaymentNotificationVerifierException;
15
use WMDE\Fundraising\Frontend\PaymentContext\RequestModel\PayPalPaymentNotificationRequest;
16
17
/**
18
 * @license GNU GPL v2+
19
 * @author Kai Nissen < [email protected] >
20
 */
21
class PayPalNotificationHandler {
22
23
	private const MSG_NOT_HANDLED = 'PayPal request not handled';
24
25
	private $ffFactory;
26
27
	public function __construct( FunFunFactory $ffFactory ) {
28
		$this->ffFactory = $ffFactory;
29
	}
30
31
	public function handle( Request $request ): Response {
32
		$post = $request->request;
33
34
		try {
35
			$this->ffFactory->getPayPalPaymentNotificationVerifier()->verify( $post->all() );
36
		} catch ( PayPalPaymentNotificationVerifierException $e ) {
37
			$this->ffFactory->getPaypalLogger()->log( LogLevel::ERROR, $e->getMessage(), [
38
				'post_vars' => $request->request->all()
39
			] );
40
			return $this->createErrorResponse( $e );
41
		}
42
43
		if ( !$this->requestIsForPaymentCompletion( $post ) ) {
44
			$this->ffFactory->getPaypalLogger()->log( LogLevel::INFO, self::MSG_NOT_HANDLED, [ 'post_vars' => $post->all() ] );
45
			return new Response( '', Response::HTTP_OK );
46
		}
47
48
		$useCase = $this->ffFactory->newHandlePayPalPaymentCompletionNotificationUseCase( $this->getUpdateToken( $post ) );
49
50
		$response = $useCase->handleNotification( $this->newUseCaseRequestFromPost( $post ) );
51
		$this->logResponseIfNeeded( $response, $request );
52
53
		return new Response( '', Response::HTTP_OK ); # PayPal expects an empty response
54
	}
55
56
	private function getUpdateToken( ParameterBag $postRequest ): string {
57
		return $this->getValueFromCustomVars( $postRequest->get( 'custom', '' ), 'utoken' );
58
	}
59
60
	private function getValueFromCustomVars( string $customVars, string $key ): string {
61
		$vars = json_decode( $customVars, true );
62
		return !empty( $vars[$key] ) ? $vars[$key] : '';
63
	}
64
65
	private function requestIsForPaymentCompletion( ParameterBag $post ): bool {
66
		return $this->isSuccessfulPaymentNotification( $post ) || (
67
				$this->isForRecurringPayment( $post ) &&
68
				!$this->isRecurringPaymentCompletion( $post )
69
			);
70
	}
71
72
	private function isSuccessfulPaymentNotification( ParameterBag $post ): bool {
73
		return $post->get( 'payment_status', '' ) === 'Completed' || $post->get( 'payment_status' ) === 'Processed';
74
	}
75
76
	private function isRecurringPaymentCompletion( ParameterBag $post ): bool {
77
		return $post->get( 'txn_type', '' ) === 'subscr_payment';
78
	}
79
80
	private function isForRecurringPayment( ParameterBag $post  ): bool {
81
		return strpos( $post->get( 'txn_type', '' ), 'subscr_' ) === 0;
82
	}
83
84
	private function newUseCaseRequestFromPost( ParameterBag $postRequest ): PayPalPaymentNotificationRequest {
85
		return ( new PayPalPaymentNotificationRequest() )
86
			->setTransactionType( $postRequest->get( 'txn_type', '' ) )
87
			->setTransactionId( $postRequest->get( 'txn_id', '' ) )
88
			->setPayerId( $postRequest->get( 'payer_id', '' ) )
89
			->setSubscriptionId( $postRequest->get( 'subscr_id', '' ) )
90
			->setPayerEmail( $postRequest->get( 'payer_email', '' ) )
91
			->setPayerStatus( $postRequest->get( 'payer_status', '' ) )
92
			->setPayerFirstName( $postRequest->get( 'first_name', '' ) )
93
			->setPayerLastName( $postRequest->get( 'last_name', '' ) )
94
			->setPayerAddressName( $postRequest->get( 'address_name', '' ) )
95
			->setPayerAddressStreet( $postRequest->get( 'address_street', '' ) )
96
			->setPayerAddressPostalCode( $postRequest->get( 'address_zip', '' ) )
97
			->setPayerAddressCity( $postRequest->get( 'address_city', '' ) )
98
			->setPayerAddressCountryCode( $postRequest->get( 'address_country_code', '' ) )
99
			->setPayerAddressStatus( $postRequest->get( 'address_status', '' ) )
100
			->setInternalId( (int)$postRequest->get( 'item_number', 0 ) )
101
			->setCurrencyCode( $postRequest->get( 'mc_currency', '' ) )
102
			->setTransactionFee( $postRequest->get( 'mc_fee', '0' ) ) // No Euro class to avoid exceptions on fees < 0
103
			->setAmountGross( Euro::newFromString( $postRequest->get( 'mc_gross', '0' ) ) )
104
			->setSettleAmount( Euro::newFromString( $postRequest->get( 'settle_amount', '0' ) ) )
105
			->setPaymentTimestamp( $postRequest->get( 'payment_date', '' ) )
106
			->setPaymentStatus( $postRequest->get( 'payment_status', '' ) )
107
			->setPaymentType( $postRequest->get( 'payment_type', '' ) );
108
	}
109
110
	private function createErrorResponse( PayPalPaymentNotificationVerifierException $e ): Response {
111
		switch ( $e->getCode() ) {
112
			case PayPalPaymentNotificationVerifierException::ERROR_WRONG_RECEIVER:
113
				return new Response( $e->getMessage(), Response::HTTP_FORBIDDEN );
114
			case PayPalPaymentNotificationVerifierException::ERROR_VERIFICATION_FAILED:
115
				return new Response( $e->getMessage(), Response::HTTP_FORBIDDEN );
116
			case PayPalPaymentNotificationVerifierException::ERROR_UNSUPPORTED_CURRENCY:
117
				return new Response( $e->getMessage(), Response::HTTP_NOT_ACCEPTABLE );
118
			default:
119
				return new Response( $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR );
120
		}
121
	}
122
123
	private function logResponseIfNeeded( PaypalNotificationResponse $response, Request $request ) {
124
		if ( $response->notificationWasHandled() ) {
125
			return;
126
		}
127
128
		$context = $response->getContext();
129
		$message = $context['message'] ?? self::MSG_NOT_HANDLED;
130
		$logLevel = $response->hasErrors() ? LogLevel::ERROR : LogLevel::INFO;
131
		unset( $context['message'] );
132
		$context['post_vars'] = $request->request->all();
133
		$this->ffFactory->getPaypalLogger()->log( $logLevel, $message, $context );
134
	}
135
136
}
137