PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Payment / FormSubmissionChargeOrchestrator.php

FormSubmissionChargeOrchestrator.php in The Innovative Form Builder – IvyForms 1.5, at backend/src/Services/Payment/FormSubmissionChargeOrchestrator.php

330 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Services\Payment;
4
5 use IvyForms\Entity\Form\Form;
6 use IvyForms\Services\Translations\BackendStrings;
7 use WP_REST_Response;
8
9 /**
10 * Charge dispatch for a public submission (amount, gateway, persist, outcome).
11 */
12 class FormSubmissionChargeOrchestrator
13 {
14 private PaymentAmountCalculator $paymentAmountCalculator;
15
16 private PaymentGatewayRegistry $paymentGatewayRegistry;
17
18 private FormSubmissionPaymentService $paymentService;
19
20 public function __construct(
21 PaymentAmountCalculator $paymentAmountCalculator,
22 PaymentGatewayRegistry $paymentGatewayRegistry,
23 FormSubmissionPaymentService $paymentService
24 ) {
25 $this->paymentAmountCalculator = $paymentAmountCalculator;
26 $this->paymentGatewayRegistry = $paymentGatewayRegistry;
27 $this->paymentService = $paymentService;
28 }
29
30 /**
31 * @param array<string, mixed> $rawParams
32 * @param array<int, object> $formFields
33 * @param array<string, mixed> $submissionData
34 * @return array<string, mixed>
35 */
36 public function process(
37 array $rawParams,
38 Form $form,
39 array $formFields,
40 array $submissionData,
41 object $paymentField
42 ): array {
43 $emptyResult = $this->emptyPaymentOutcome();
44 $submissionByTypeIndex = $this->mapSubmissionToTypeIndexKeys($submissionData, $formFields);
45 $prepared = $this->prepareChargeableRequest(
46 $form,
47 $formFields,
48 $submissionByTypeIndex,
49 $paymentField,
50 $rawParams,
51 $emptyResult
52 );
53 if ($prepared['errorResponse'] !== null) {
54 return $prepared;
55 }
56
57 return $this->runGatewayCharge(
58 $rawParams,
59 $form,
60 $formFields,
61 $submissionData,
62 $paymentField,
63 $submissionByTypeIndex,
64 $prepared,
65 $emptyResult
66 );
67 }
68
69 /**
70 * @return array<string, mixed>
71 */
72 private function emptyPaymentOutcome(): array
73 {
74 return [
75 'errorResponse' => null,
76 'paymentResult' => null,
77 'paymentTotal' => 0.0,
78 'paymentCurrency' => 'USD',
79 'paymentMethod' => 'square',
80 'squareOrderId' => '',
81 'squareCustomerId' => null,
82 'customerName' => null,
83 'requiresAction' => false,
84 'clientSecret' => null,
85 'paymentIntentId' => null,
86 ];
87 }
88
89 /**
90 * @param array<int, object> $formFields
91 * @param array<string, mixed> $submissionByTypeIndex
92 * @param array<string, mixed> $rawParams
93 * @param array<string, mixed> $emptyResult
94 * @return array<string, mixed>
95 */
96 private function prepareChargeableRequest(
97 Form $form,
98 array $formFields,
99 array $submissionByTypeIndex,
100 object $paymentField,
101 array $rawParams,
102 array $emptyResult
103 ): array {
104 $currency = strtoupper((string) $form->getPaymentSettings()->getCurrency());
105 if ($currency === '') {
106 $currency = 'USD';
107 }
108
109 $total = $this->paymentAmountCalculator->calculateTotal(
110 array_map(static fn($field) => $field->toArray(), $formFields),
111 $submissionByTypeIndex,
112 $currency
113 );
114 if (!MoneyMinorUnits::isChargeable($total, $currency)) {
115 return array_merge($emptyResult, [
116 'errorResponse' => $this->paymentErrorResponse(
117 BackendStrings::getPaymentStrings()['invalid_payment_amount']
118 ),
119 ]);
120 }
121
122 $gatewayId = $this->resolveGatewayId($paymentField, $rawParams);
123 if ($gatewayId === null) {
124 return array_merge($emptyResult, [
125 'errorResponse' => $this->paymentErrorResponse(
126 BackendStrings::getPaymentStrings()['payment_gateway_unavailable']
127 ),
128 ]);
129 }
130
131 return array_merge($emptyResult, [
132 'total' => $total,
133 'gatewayId' => $gatewayId,
134 ]);
135 }
136
137 /**
138 * @param array<string, mixed> $rawParams
139 * @param array<int, object> $formFields
140 * @param array<string, mixed> $submissionData
141 * @param array<string, mixed> $submissionByTypeIndex
142 * @param array<string, mixed> $prepared
143 * @param array<string, mixed> $emptyResult
144 * @return array<string, mixed>
145 */
146 private function runGatewayCharge(
147 array $rawParams,
148 Form $form,
149 array $formFields,
150 array $submissionData,
151 object $paymentField,
152 array $submissionByTypeIndex,
153 array $prepared,
154 array $emptyResult
155 ): array {
156 $gatewayId = (string) $prepared['gatewayId'];
157 try {
158 $gateway = $this->paymentGatewayRegistry->get($gatewayId);
159 } catch (\Throwable $e) {
160 return array_merge($emptyResult, [
161 'paymentMethod' => $gatewayId,
162 'errorResponse' => $this->paymentErrorResponse(
163 BackendStrings::getPaymentStrings()['payment_gateway_unavailable']
164 ),
165 ]);
166 }
167
168 $chargeResult = $gateway->processCharge(
169 $rawParams,
170 $form,
171 $formFields,
172 $submissionData,
173 $paymentField,
174 (float) $prepared['total']
175 );
176 if ($chargeResult->isFailed()) {
177 return $this->failedChargeOutcome($chargeResult, $form, $emptyResult);
178 }
179
180 return $this->successfulChargeOutcome(
181 $chargeResult,
182 $form,
183 $gatewayId,
184 $formFields,
185 $submissionData,
186 $submissionByTypeIndex,
187 $emptyResult
188 );
189 }
190
191 /**
192 * @param array<string, mixed> $emptyResult
193 * @return array<string, mixed>
194 */
195 private function failedChargeOutcome(
196 PaymentChargeResult $chargeResult,
197 Form $form,
198 array $emptyResult
199 ): array {
200 $this->paymentService->persistChargeFromResult($form, $chargeResult, $chargeResult->getStatus());
201
202 return array_merge($emptyResult, [
203 'paymentMethod' => $chargeResult->getPaymentMethod(),
204 'errorResponse' => $this->paymentErrorResponse(
205 (string) ($chargeResult->getMessage() ?? BackendStrings::getPaymentStrings()['payment_failed'])
206 ),
207 ]);
208 }
209
210 /**
211 * @param array<int, object> $formFields
212 * @param array<string, mixed> $submissionData
213 * @param array<string, mixed> $submissionByTypeIndex
214 * @param array<string, mixed> $emptyResult
215 * @return array<string, mixed>
216 */
217 private function successfulChargeOutcome(
218 PaymentChargeResult $chargeResult,
219 Form $form,
220 string $gatewayId,
221 array $formFields,
222 array $submissionData,
223 array $submissionByTypeIndex,
224 array $emptyResult
225 ): array {
226 $customerName = SubmissionPaymentCustomerName::resolve(
227 $chargeResult->getCustomerName(),
228 $form,
229 $gatewayId,
230 $formFields,
231 $submissionData,
232 $submissionByTypeIndex
233 );
234 $this->paymentService->persistChargeFromResult(
235 $form,
236 $chargeResult,
237 FormSubmissionPaymentService::STATUS_PENDING,
238 $customerName
239 );
240
241 if ($chargeResult->isRequiresAction()) {
242 return array_merge($emptyResult, [
243 'paymentMethod' => $chargeResult->getPaymentMethod(),
244 'paymentTotal' => $chargeResult->getPaymentTotal(),
245 'paymentCurrency' => $chargeResult->getPaymentCurrency(),
246 'customerName' => $customerName,
247 'requiresAction' => true,
248 'clientSecret' => $chargeResult->getClientSecret(),
249 'paymentIntentId' => $chargeResult->getPaymentIntentId(),
250 'paymentResult' => [
251 'transactionId' => (string) ($chargeResult->getTransactionId() ?? ''),
252 'paymentMethod' => $chargeResult->getPaymentMethod(),
253 'status' => $chargeResult->getStatus(),
254 ],
255 ]);
256 }
257
258 return [
259 'errorResponse' => null,
260 'paymentResult' => $chargeResult->toPaymentResultArray(),
261 'paymentTotal' => $chargeResult->getPaymentTotal(),
262 'paymentCurrency' => $chargeResult->getPaymentCurrency(),
263 'paymentMethod' => $chargeResult->getPaymentMethod(),
264 'squareOrderId' => (string) ($chargeResult->getSquareOrderId() ?? ''),
265 'squareCustomerId' => $chargeResult->getSquareCustomerId(),
266 'customerName' => $customerName,
267 'requiresAction' => false,
268 'clientSecret' => null,
269 'paymentIntentId' => null,
270 ];
271 }
272
273 /**
274 * @param array<string, mixed> $rawParams
275 */
276 private function resolveGatewayId(object $paymentField, array $rawParams): ?string
277 {
278 $fromToken = PaymentMethodFieldConfig::resolveGatewayIdFromToken($rawParams, $paymentField);
279 if ($fromToken !== null) {
280 return $this->paymentGatewayRegistry->has($fromToken) ? $fromToken : null;
281 }
282
283 if (PaymentMethodFieldConfig::resolveGatewayIdFromToken($rawParams) !== null) {
284 return null;
285 }
286
287 return PaymentMethodFieldConfig::resolvePrimaryGatewayId($paymentField);
288 }
289
290 /**
291 * @param array<string, mixed> $submissionData
292 * @param array<int, object> $formFields
293 * @return array<string, mixed>
294 */
295 private function mapSubmissionToTypeIndexKeys(array $submissionData, array $formFields): array
296 {
297 $mapped = [];
298
299 foreach ($formFields as $field) {
300 if (!method_exists($field, 'getType') || !method_exists($field, 'getIndex')) {
301 continue;
302 }
303
304 $fieldId = method_exists($field, 'getId') ? $field->getId() : null;
305 $type = (string) $field->getType();
306 $index = (int) $field->getIndex();
307 $key = $type . '_' . $index;
308
309 if ($fieldId !== null && isset($submissionData[$fieldId])) {
310 $mapped[$key] = $submissionData[$fieldId];
311 } elseif (isset($submissionData[$key])) {
312 $mapped[$key] = $submissionData[$key];
313 }
314 }
315
316 return $mapped;
317 }
318
319 private function paymentErrorResponse(string $message): WP_REST_Response
320 {
321 return new WP_REST_Response([
322 'message' => $message,
323 'data' => [
324 'success' => false,
325 'payment_error' => true,
326 ],
327 ], 400);
328 }
329 }
330