Passed
Push — master ( 489f2e...0cdf36 )
by
unknown
289:17 queued 286:28
created

requestIsForPaymentCompletion()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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