Passed
Push — master ( 957118...17714f )
by
unknown
64:52 queued 10s
created

newBankDataFromAccountAndBankCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\App\Controllers\Donation;
6
7
use Symfony\Component\HttpFoundation\RedirectResponse;
8
use Symfony\Component\HttpFoundation\Request;
9
use Symfony\Component\HttpFoundation\Response;
10
use Symfony\Component\HttpFoundation\Session\SessionInterface;
11
use WMDE\Euro\Euro;
12
use WMDE\Fundraising\DonationContext\Domain\Model\DonationTrackingInfo;
13
use WMDE\Fundraising\DonationContext\Domain\Model\DonorType;
14
use WMDE\Fundraising\DonationContext\UseCases\AddDonation\AddDonationRequest;
15
use WMDE\Fundraising\DonationContext\UseCases\AddDonation\AddDonationResponse;
16
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
17
use WMDE\Fundraising\Frontend\Infrastructure\AddressType;
18
use WMDE\Fundraising\Frontend\Infrastructure\Validation\FallbackRequestValueReader;
19
use WMDE\Fundraising\PaymentContext\Domain\Model\BankData;
20
use WMDE\Fundraising\PaymentContext\Domain\Model\Iban;
21
use WMDE\Fundraising\PaymentContext\Domain\Model\PaymentMethod;
22
use WMDE\FunValidators\ConstraintViolation;
23
24
/**
25
 * @license GPL-2.0-or-later
26
 */
27
class AddDonationController {
28
29
	private SessionInterface $session;
30
	private FunFunFactory $ffFactory;
31
	/**
32
	 * @var FallbackRequestValueReader
33
	 */
34
	private FallbackRequestValueReader $legacyRequestValueReader;
35
36
	public function index( FunFunFactory $ffFactory, Request $request, SessionInterface $session ): Response {
37
		$this->session = $session;
38
		$this->ffFactory = $ffFactory;
39
		if ( !$ffFactory->getDonationSubmissionRateLimiter()->isSubmissionAllowed( $session ) ) {
40
			return new Response( $this->ffFactory->newSystemMessageResponse( 'donation_rejected_limit' ) );
41
		}
42
43
		$this->legacyRequestValueReader = new FallbackRequestValueReader( $ffFactory->getLogger(), $request );
44
		$addDonationRequest = $this->createDonationRequest( $request );
45
		$responseModel = $this->ffFactory->newAddDonationUseCase()->addDonation( $addDonationRequest );
46
47
		if ( !$responseModel->isSuccessful() ) {
48
			$this->logValidationErrors( $responseModel->getValidationErrors() );
49
			return new Response(
50
				$this->ffFactory->newDonationFormViolationPresenter()->present(
51
					$responseModel->getValidationErrors(),
52
					$addDonationRequest,
53
					$this->newTrackingInfoFromRequest( $request )
54
				)
55
			);
56
		}
57
58
		$this->resetAddressChangeDataInSession();
59
60
		return $this->newHttpResponse( $session, $responseModel );
61
	}
62
63
	private function newHttpResponse( SessionInterface $session, AddDonationResponse $responseModel ): Response {
64
		$this->ffFactory->getDonationSubmissionRateLimiter()->setRateLimitCookie( $session );
65
		switch ( $responseModel->getDonation()->getPaymentMethodId() ) {
66
			case PaymentMethod::DIRECT_DEBIT:
67
			case PaymentMethod::BANK_TRANSFER:
68
				return new RedirectResponse(
69
					$this->ffFactory->getUrlGenerator()->generateAbsoluteUrl(
70
						'show-donation-confirmation',
71
						[
72
							'id' => $responseModel->getDonation()->getId(),
73
							'accessToken' => $responseModel->getAccessToken()
74
						]
75
					)
76
				);
77
			case PaymentMethod::PAYPAL:
78
				return new RedirectResponse(
79
					$this->ffFactory->newPayPalUrlGeneratorForDonations()->generateUrl(
80
						$responseModel->getDonation()->getId(),
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getDonation()->getId() can also be of type null; however, parameter $itemId of WMDE\Fundraising\Payment...r\PayPal::generateUrl() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

80
						/** @scrutinizer ignore-type */ $responseModel->getDonation()->getId(),
Loading history...
81
						$responseModel->getDonation()->getAmount(),
82
						$responseModel->getDonation()->getPaymentIntervalInMonths(),
83
						$responseModel->getUpdateToken(),
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getUpdateToken() can also be of type null; however, parameter $updateToken of WMDE\Fundraising\Payment...r\PayPal::generateUrl() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
						/** @scrutinizer ignore-type */ $responseModel->getUpdateToken(),
Loading history...
84
						$responseModel->getAccessToken()
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getAccessToken() can also be of type null; however, parameter $accessToken of WMDE\Fundraising\Payment...r\PayPal::generateUrl() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
						/** @scrutinizer ignore-type */ $responseModel->getAccessToken()
Loading history...
85
					)
86
				);
87
			case PaymentMethod::SOFORT:
88
				return new RedirectResponse(
89
					$this->ffFactory->newSofortUrlGeneratorForDonations()->generateUrl(
90
						$responseModel->getDonation()->getId(),
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getDonation()->getId() can also be of type null; however, parameter $internalItemId of WMDE\Fundraising\Payment...r\Sofort::generateUrl() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
						/** @scrutinizer ignore-type */ $responseModel->getDonation()->getId(),
Loading history...
91
						$responseModel->getDonation()->getPayment()->getPaymentMethod()->getBankTransferCode(),
92
						$responseModel->getDonation()->getAmount(),
93
						$responseModel->getUpdateToken(),
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getUpdateToken() can also be of type null; however, parameter $updateToken of WMDE\Fundraising\Payment...r\Sofort::generateUrl() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
						/** @scrutinizer ignore-type */ $responseModel->getUpdateToken(),
Loading history...
94
						$responseModel->getAccessToken()
0 ignored issues
show
Bug introduced by
It seems like $responseModel->getAccessToken() can also be of type null; however, parameter $accessToken of WMDE\Fundraising\Payment...r\Sofort::generateUrl() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

94
						/** @scrutinizer ignore-type */ $responseModel->getAccessToken()
Loading history...
95
					)
96
				);
97
			case PaymentMethod::CREDIT_CARD:
98
				return new RedirectResponse(
99
					$this->ffFactory->newCreditCardPaymentUrlGenerator()->buildUrl( $responseModel )
100
				);
101
			default:
102
				throw new \LogicException( 'Unknown Payment method - can\'t determine response' );
103
		}
104
	}
105
106
	private function createDonationRequest( Request $request ): AddDonationRequest {
107
		$donationRequest = new AddDonationRequest();
108
109
		// TODO Replace legacyRequestValueReader with default values after January 2021
110
		$donationRequest->setAmount( $this->getEuroAmount( $this->getAmountFromRequest( $request ) ) );
111
		$donationRequest->setPaymentType( $request->get( 'paymentType', $this->legacyRequestValueReader->getPaymentType() ) );
112
		$donationRequest->setInterval( intval( $request->get( 'interval', $this->legacyRequestValueReader->getInterval() ) ) );
113
114
		$donationRequest->setDonorType( $this->getSafeDonorType( $request ) );
115
		$donationRequest->setDonorSalutation( $request->get( 'salutation', '' ) );
116
		$donationRequest->setDonorTitle( $request->get( 'title', '' ) );
117
		$donationRequest->setDonorCompany( $request->get( 'companyName', '' ) );
118
		$donationRequest->setDonorFirstName( $request->get( 'firstName', '' ) );
119
		$donationRequest->setDonorLastName( $request->get( 'lastName', '' ) );
120
		$donationRequest->setDonorStreetAddress( $this->filterAutofillCommas( $request->get( 'street', '' ) ) );
121
		$donationRequest->setDonorPostalCode( $request->get( 'postcode', '' ) );
122
		$donationRequest->setDonorCity( $request->get( 'city', '' ) );
123
		$donationRequest->setDonorCountryCode( $request->get( 'country', '' ) );
124
		$donationRequest->setDonorEmailAddress( $request->get( 'email', '' ) );
125
126
		if ( $donationRequest->getPaymentType() === PaymentMethod::DIRECT_DEBIT ) {
127
			$donationRequest->setBankData( $this->getBankDataFromRequest( $request ) );
128
		}
129
130
		$donationRequest->setTracking( $request->attributes->get( 'trackingCode' ) );
131
		$donationRequest->setOptIn( $request->get( 'info', '' ) );
132
		// Setting source for completeness sake,
133
		// TODO Remove when  https://phabricator.wikimedia.org/T134327 is done
134
		$donationRequest->setSource( '' );
135
		$donationRequest->setTotalImpressionCount( intval( $request->get( 'impCount', 0 ) ) );
136
		$donationRequest->setSingleBannerImpressionCount( intval( $request->get( 'bImpCount', 0 ) ) );
137
		$donationRequest->setOptsIntoDonationReceipt( $request->request->getBoolean( 'donationReceipt', true ) );
138
139
		return $donationRequest;
140
	}
141
142
	private function getBankDataFromRequest( Request $request ): BankData {
143
		$bankData = new BankData();
144
		$bankData->setIban( new Iban( $request->get( 'iban', '' ) ) )
145
			->setBic( $request->get( 'bic', '' ) )
146
			->setAccount( $request->get( 'konto', '' ) )
147
			->setBankCode( $request->get( 'blz', '' ) )
148
			->setBankName( $request->get( 'bankname', '' ) );
149
150
		if ( $bankData->isComplete() ) {
151
			return $bankData->freeze()->assertNoNullFields();
152
		}
153
154
		if ( $bankData->hasIban() ) {
155
			$bankData = $this->newBankDataFromIban( $bankData->getIban() );
156
		} elseif ( $bankData->hasCompleteLegacyBankData() ) {
157
			$bankData = $this->newBankDataFromAccountAndBankCode( $bankData->getAccount(), $bankData->getBankCode() );
158
		}
159
		return $bankData->freeze()->assertNoNullFields();
160
	}
161
162
	private function newBankDataFromIban( Iban $iban ): BankData {
163
		return $this->ffFactory->newBankDataConverter()->getBankDataFromIban( $iban );
164
	}
165
166
	private function newBankDataFromAccountAndBankCode( string $account, string $bankCode ): BankData {
167
		return $this->ffFactory->newBankDataConverter()->getBankDataFromAccountData( $account, $bankCode );
168
	}
169
170
	private function getEuroAmount( int $amount ): Euro {
171
		try {
172
			return Euro::newFromCents( $amount );
173
		} catch ( \InvalidArgumentException $ex ) {
174
			return Euro::newFromCents( 0 );
175
		}
176
	}
177
178
	private function getAmountFromRequest( Request $request ): int {
179
		if ( $request->request->has( 'amount' ) ) {
180
			return intval( $request->get( 'amount' ) );
181
		}
182
		return $this->legacyRequestValueReader->getAmount();
183
	}
184
185
	private function newTrackingInfoFromRequest( Request $request ): DonationTrackingInfo {
186
		$tracking = new DonationTrackingInfo();
187
		$tracking->setSingleBannerImpressionCount( intval( $request->get( 'bImpCount', 0 ) ) );
188
		$tracking->setTotalImpressionCount( intval( $request->get( 'impCount', 0 ) ) );
189
190
		return $tracking;
191
	}
192
193
	/**
194
	 * Safari and Chrome concatenate street autofill values (e.g. house number and street name) with a comma.
195
	 * This method removes the commas.
196
	 *
197
	 * @param string $value
198
	 * @return string
199
	 */
200
	private function filterAutofillCommas( string $value ): string {
201
		return trim( preg_replace( [ '/,/', '/\s{2,}/' ], [ ' ', ' ' ], $value ) );
202
	}
203
204
	/**
205
	 * Reset session data to prevent old donations from changing the application output due to old data leaking into the new session
206
	 */
207
	private function resetAddressChangeDataInSession(): void {
208
		$this->session->set(
209
			UpdateDonorController::ADDRESS_CHANGE_SESSION_KEY,
210
			false
211
		);
212
	}
213
214
	/**
215
	 * @param ConstraintViolation[] $constraintViolations
216
	 */
217
	private function logValidationErrors( array $constraintViolations ): void {
218
		$fields = [];
219
		$formattedConstraintViolations = [];
220
		foreach ( $constraintViolations as $constraintViolation ) {
221
			$source = $constraintViolation->getSource();
222
			$fields[] = $source;
223
			$formattedConstraintViolations['validation_errors'][] = sprintf(
224
				'Validation field "%s" with value "%s" failed with: %s',
225
				$source,
226
				$constraintViolation->getValue(),
227
				$constraintViolation->getMessageIdentifier()
228
			);
229
		}
230
231
		$this->ffFactory->getValidationErrorLogger()->logViolations(
232
			'Unexpected server-side form validation errors.',
233
			$fields,
234
			$formattedConstraintViolations
235
		);
236
	}
237
238
	/**
239
	 * Get AddDonationRequest donor type from HTTP request field.
240
	 *
241
	 * Assumes "Anonymous" when field is not set or invalid.
242
	 *
243
	 * @param Request $request
244
	 *
245
	 * @return DonorType
246
	 */
247
	private function getSafeDonorType( Request $request ): DonorType {
248
		try {
249
			return DonorType::make(
250
				AddressType::presentationAddressTypeToDomainAddressType( $request->get( 'addressType', '' ) )
251
			);
252
		} catch ( \UnexpectedValueException $e ) {
253
			return DonorType::ANONYMOUS();
254
		}
255
	}
256
}
257