Passed
Push — master ( de9f22...184dfe )
by Jeroen De
02:28
created

AddDonationHandler::createDonationRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 24
nc 2
nop 1
dl 0
loc 32
ccs 25
cts 25
cp 1
crap 2
rs 9.536
c 0
b 0
f 0
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\App\RouteHandlers;
6
7
use Silex\Application;
8
use Symfony\Component\HttpFoundation\Request;
9
use Symfony\Component\HttpFoundation\Response;
10
use WMDE\Euro\Euro;
11
use WMDE\Fundraising\DonationContext\Domain\Model\DonationTrackingInfo;
12
use WMDE\Fundraising\DonationContext\UseCases\AddDonation\AddDonationRequest;
13
use WMDE\Fundraising\DonationContext\UseCases\AddDonation\AddDonationResponse;
14
use WMDE\Fundraising\Frontend\App\Controllers\ShowDonationConfirmationController;
15
use WMDE\Fundraising\Frontend\BucketTesting\Logging\Events\DonationCreated;
16
use WMDE\Fundraising\Frontend\App\Controllers\UpdateDonorController;
17
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
18
use WMDE\Fundraising\Frontend\Infrastructure\AmountParser;
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
23
/**
24
 * @license GNU GPL v2+
25
 * @author Kai Nissen < [email protected] >
26
 * @author Jeroen De Dauw < [email protected] >
27
 */
28
class AddDonationHandler {
29
30
	private $ffFactory;
31
	private $app;
32
33 26
	public function __construct( FunFunFactory $ffFactory, Application $app ) {
34 26
		$this->ffFactory = $ffFactory;
35 26
		$this->app = $app;
36 26
	}
37
38 26
	public function handle( Request $request ): Response {
39 26
		if ( !$this->isSubmissionAllowed( $request ) ) {
40 1
			return new Response( $this->ffFactory->newSystemMessageResponse( 'donation_rejected_limit' ) );
41
		}
42
43 25
		$addDonationRequest = $this->createDonationRequest( $request );
44 25
		$responseModel = $this->ffFactory->newAddDonationUseCase()->addDonation( $addDonationRequest );
45
46 25
		if ( !$responseModel->isSuccessful() ) {
47 4
			return new Response(
48 4
				$this->ffFactory->newDonationFormViolationPresenter()->present(
49 4
					$responseModel->getValidationErrors(), $addDonationRequest,
50 4
					$this->newTrackingInfoFromRequest( $request )
51
				)
52
			);
53
		}
54
55 21
		$this->logSelectedBuckets( $responseModel );
56 21
		$this->sendTrackingDataIfNeeded( $request, $responseModel );
57 21
		$this->resetSessionState();
58
59 21
		return $this->newHttpResponse( $responseModel );
60
	}
61
62 21
	private function sendTrackingDataIfNeeded( Request $request, AddDonationResponse $responseModel ) {
63 21
		if ( $request->get( 'mbt', '' ) !== '1' || !$responseModel->getDonation()->hasExternalPayment() ) {
64 20
			return;
65
		}
66
67 1
		$trackingCode = explode( '/', $request->attributes->get( 'trackingCode' ) );
68 1
		$campaign = $trackingCode[0];
69 1
		$keyword = $trackingCode[1] ?? '';
70
71 1
		$this->ffFactory->getPageViewTracker()->trackPaypalRedirection( $campaign, $keyword, $request->getClientIp() );
72 1
	}
73
74 21
	private function newHttpResponse( AddDonationResponse $responseModel ): Response {
75 21
		switch( $responseModel->getDonation()->getPaymentMethodId() ) {
76 21
			case PaymentMethod::DIRECT_DEBIT:
77 9
			case PaymentMethod::BANK_TRANSFER:
78 15
				return $this->app->redirect(
79 15
					$this->app['url_generator']->generate(
80 15
						'show-donation-confirmation',
81
						[
82 15
							'id' => $responseModel->getDonation()->getId(),
83 15
							'accessToken' => $responseModel->getAccessToken()
84
						]
85
					),
86 15
					Response::HTTP_SEE_OTHER
87
				);
88
				break;
89 6
			case PaymentMethod::PAYPAL:
90 3
				return $this->app->redirect(
91 3
					$this->ffFactory->newPayPalUrlGeneratorForDonations()->generateUrl(
92 3
						$responseModel->getDonation()->getId(),
93 3
						$responseModel->getDonation()->getAmount(),
94 3
						$responseModel->getDonation()->getPaymentIntervalInMonths(),
95 3
						$responseModel->getUpdateToken(),
96 3
						$responseModel->getAccessToken()
97
					)
98
				);
99
				break;
100 3
			case PaymentMethod::SOFORT:
101 1
				return $this->app->redirect(
102 1
					$this->ffFactory->newSofortUrlGeneratorForDonations()->generateUrl(
103 1
						$responseModel->getDonation()->getId(),
104 1
						$responseModel->getDonation()->getPayment()->getPaymentMethod()->getBankTransferCode(),
105 1
						$responseModel->getDonation()->getAmount(),
106 1
						$responseModel->getUpdateToken(),
107 1
						$responseModel->getAccessToken()
108
					)
109
				);
110
				break;
111 2
			case PaymentMethod::CREDIT_CARD:
112 2
				return $this->app->redirect(
113 2
					$this->ffFactory->newCreditCardPaymentUrlGenerator()->buildUrl( $responseModel )
114
				);
115
				break;
116
			default:
117
				throw new \LogicException( 'This code should not be reached' );
118
		}
119
	}
120
121 25
	private function createDonationRequest( Request $request ): AddDonationRequest {
122 25
		$donationRequest = new AddDonationRequest();
123
124 25
		$donationRequest->setAmount( $this->getEuroAmountFromString( $request->get( 'betrag', '' ) ) );
125
126 25
		$donationRequest->setPaymentType( $request->get( 'zahlweise', '' ) );
127 25
		$donationRequest->setInterval( intval( $request->get( 'periode', 0 ) ) );
128
129 25
		$donationRequest->setDonorType( $request->get( 'addressType', '' ) );
130 25
		$donationRequest->setDonorSalutation( $request->get( 'salutation', '' ) );
131 25
		$donationRequest->setDonorTitle( $request->get( 'title', '' ) );
132 25
		$donationRequest->setDonorCompany( $request->get( 'companyName', '' ) );
133 25
		$donationRequest->setDonorFirstName( $request->get( 'firstName', '' ) );
134 25
		$donationRequest->setDonorLastName( $request->get( 'lastName', '' ) );
135 25
		$donationRequest->setDonorStreetAddress( $this->filterAutofillCommas( $request->get( 'street', '' ) ) );
136 25
		$donationRequest->setDonorPostalCode( $request->get( 'postcode', '' ) );
137 25
		$donationRequest->setDonorCity( $request->get( 'city', '' ) );
138 25
		$donationRequest->setDonorCountryCode( $request->get( 'country', '' ) );
139 25
		$donationRequest->setDonorEmailAddress( $request->get( 'email', '' ) );
140
141 25
		if ( $request->get( 'zahlweise', '' ) === PaymentMethod::DIRECT_DEBIT ) {
142 14
			$donationRequest->setBankData( $this->getBankDataFromRequest( $request ) );
143
		}
144
145 25
		$donationRequest->setTracking( $request->attributes->get( 'trackingCode' ) );
146 25
		$donationRequest->setOptIn( $request->get( 'info', '' ) );
147 25
		$donationRequest->setSource( $request->attributes->get( 'trackingSource' ) );
148 25
		$donationRequest->setTotalImpressionCount( intval( $request->get( 'impCount', 0 ) ) );
149 25
		$donationRequest->setSingleBannerImpressionCount( intval( $request->get( 'bImpCount', 0 ) ) );
150 25
		$donationRequest->setOptsIntoDonationReceipt( $request->request->getBoolean( 'donationReceipt', true ) );
151
152 25
		return $donationRequest;
153
	}
154
155 14
	private function getBankDataFromRequest( Request $request ): BankData {
156 14
		$bankData = new BankData();
157 14
		$bankData->setIban( new Iban( $request->get( 'iban', '' ) ) )
158 14
			->setBic( $request->get( 'bic', '' ) )
159 14
			->setAccount( $request->get( 'konto', '' ) )
160 14
			->setBankCode( $request->get( 'blz', '' ) )
161 14
			->setBankName( $request->get( 'bankname', '' ) );
162
163 14
		if ( $bankData->isComplete() ) {
164 11
			return $bankData->freeze()->assertNoNullFields();
165
		}
166
167 3
		if ( $bankData->hasIban() ) {
168 3
			$bankData = $this->newBankDataFromIban( $bankData->getIban() );
169
		} elseif ( $bankData->hasCompleteLegacyBankData() ) {
170
			$bankData = $this->newBankDataFromAccountAndBankCode( $bankData->getAccount(), $bankData->getBankCode() );
171
		}
172 3
		return $bankData->freeze()->assertNoNullFields();
173
	}
174
175 3
	private function newBankDataFromIban( Iban $iban ): BankData {
176 3
		return $this->ffFactory->newBankDataConverter()->getBankDataFromIban( $iban );
177
	}
178
179
	private function newBankDataFromAccountAndBankCode( string $account, string $bankCode ): BankData {
180
		return $this->ffFactory->newBankDataConverter()->getBankDataFromAccountData( $account, $bankCode );
181
	}
182
183 25
	private function getEuroAmountFromString( string $amount ): Euro {
184 25
		$locale = 'de_DE'; // TODO: make this configurable for multilanguage support
185
		try {
186 25
			return Euro::newFromFloat( ( new AmountParser( $locale ) )->parseAsFloat( $amount ) );
187 1
		} catch ( \InvalidArgumentException $ex ) {
188 1
			return Euro::newFromCents( 0 );
189
		}
190
191
	}
192
193 26
	private function isSubmissionAllowed( Request $request ) {
194 26
		$lastSubmission = $request->cookies->get( ShowDonationConfirmationController::SUBMISSION_COOKIE_NAME, '' );
195 26
		if ( $lastSubmission === '' ) {
196 24
			return true;
197
		}
198
199
		$minNextTimestamp =
200 2
			\DateTime::createFromFormat( ShowDonationConfirmationController::TIMESTAMP_FORMAT, $lastSubmission )
201 2
			->add( new \DateInterval( $this->ffFactory->getDonationTimeframeLimit() ) );
202
203 2
		if ( $minNextTimestamp > new \DateTime() ) {
204 1
			return false;
205
		}
206
207 1
		return true;
208
	}
209
210 4
	private function newTrackingInfoFromRequest( Request $request ): DonationTrackingInfo {
211 4
		$tracking = new DonationTrackingInfo();
212 4
		$tracking->setSingleBannerImpressionCount( intval( $request->get( 'bImpCount', 0 ) ) );
213 4
		$tracking->setTotalImpressionCount( intval( $request->get( 'impCount', 0 ) ) );
214
215 4
		return $tracking;
216
	}
217
218
	/**
219
	 * Safari and Chrome concatenate street autofill values (e.g. house number and street name) with a comma.
220
	 * This method removes the commas.
221
	 *
222
	 * @param string $value
223
	 * @return string
224
	 */
225 25
	private function filterAutofillCommas( string $value ): string {
226 25
		return trim( preg_replace( ['/,/', '/\s{2,}/'], [' ', ' '], $value ) );
227
	}
228
229 21
	private function logSelectedBuckets( AddDonationResponse $responseModel ): void {
230 21
		$this->ffFactory->getBucketLogger()->writeEvent(
231 21
			new DonationCreated( $responseModel->getDonation()->getId() ),
232 21
			...$this->ffFactory->getSelectedBuckets()
233
		);
234 21
	}
235
236
	/**
237
	 * Reset session data to prevent old donations from changing the application output due to old data leaking into the new session
238
	 */
239 21
	private function resetSessionState(): void {
240 21
		$this->app['session']->set(
241 21
			UpdateDonorController::ADDRESS_CHANGE_SESSION_KEY,
242 21
			false
243
		);
244
	}
245
}