PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.16.8.1
GiveWP – Donation Plugin and Fundraising Platform v4.16.8.1
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / src / API / REST / V3 / Routes / Donations / DataTransferObjects / DonationCreateData.php

DonationCreateData.php in GiveWP – Donation Plugin and Fundraising Platform 4.16.8.1, at src/API/REST/V3/Routes/Donations/DataTransferObjects/DonationCreateData.php

426 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\API\REST\V3\Routes\Donations\DataTransferObjects;
4
5 use Exception;
6 use Give\API\REST\V3\Routes\Donations\Exceptions\DonationValidationException;
7 use Give\API\REST\V3\Routes\Donations\Fields\DonationFields;
8 use Give\Donations\Models\Donation;
9 use Give\Donations\ValueObjects\DonationType;
10 use Give\Subscriptions\Models\Subscription;
11 use WP_REST_Request;
12
13 /**
14 * @since 4.8.0
15 */
16 class DonationCreateData
17 {
18 /**
19 * @var array
20 */
21 private $attributes;
22
23 /**
24 * @var bool
25 */
26 private $isRenewal;
27
28 /**
29 * @var DonationType|null
30 */
31 private $type;
32
33 /**
34 * @var int
35 */
36 private $subscriptionId;
37
38 /**
39 * @var bool
40 */
41 private $updateRenewalDate;
42
43 /**
44 * @since 3.0.0
45 */
46 public function __construct(array $attributes)
47 {
48 // Extract updateRenewalDate before processing attributes
49 $this->updateRenewalDate = $attributes['updateRenewalDate'] ?? false;
50
51 $this->attributes = $this->processAttributes($attributes);
52 $this->subscriptionId = $this->attributes['subscriptionId'] ?? 0;
53 $this->type = $this->attributes['type'] ?? null;
54 $this->isRenewal = $this->determineIfRenewal();
55 }
56
57 /**
58 * Create DonationCreateData from REST request
59 *
60 * @since 4.8.0
61 *
62 * @param WP_REST_Request $request
63 * @return DonationCreateData
64 */
65 public static function fromRequest(WP_REST_Request $request): DonationCreateData
66 {
67 return new self($request->get_params());
68 }
69
70 /**
71 * Validate data for creating a single donation
72 *
73 * @since 4.8.0
74 *
75 * @throws DonationValidationException
76 */
77 public function validateCreateDonation(): void
78 {
79 if ($this->isRenewal) {
80 throw new DonationValidationException(
81 __('Cannot create single donation for renewal type', 'give'),
82 'invalid_donation_type',
83 400
84 );
85 }
86
87 $requiredFields = ['donorId', 'amount', 'gatewayId', 'mode', 'formId', 'firstName', 'email'];
88
89 foreach ($requiredFields as $field) {
90 if (!isset($this->attributes[$field])) {
91 throw new DonationValidationException(
92 sprintf(__('Missing required field: %s', 'give'), $field),
93 'missing_required_field',
94 400
95 );
96 }
97 }
98 }
99
100 /**
101 * Validate data for creating a renewal donation
102 *
103 * @since 4.8.0
104 *
105 * @throws DonationValidationException
106 */
107 public function validateCreateRenewal(): void
108 {
109 if (!$this->isRenewal) {
110 throw new DonationValidationException(
111 __('Cannot create renewal donation for non-renewal type', 'give'),
112 'invalid_donation_type',
113 400
114 );
115 }
116
117 $requiredFields = ['subscriptionId', 'type'];
118
119 foreach ($requiredFields as $field) {
120 if (!isset($this->attributes[$field])) {
121 throw new DonationValidationException(
122 sprintf(__('Missing required field: %s', 'give'), $field),
123 'missing_required_field',
124 400
125 );
126 }
127 }
128
129 // Validate subscription exists
130 $subscription = Subscription::find($this->subscriptionId);
131 if (!$subscription) {
132 throw new DonationValidationException(
133 __('Subscription not found', 'give'),
134 'subscription_not_found',
135 404
136 );
137 }
138
139 // Ensure total donations don't exceed subscription installments
140 if ($subscription->installments > 0 && $subscription->totalDonations() >= $subscription->installments) {
141 throw new DonationValidationException(
142 __('Cannot create donation: subscription installments limit reached', 'give'),
143 'subscription_installments_exceeded',
144 400
145 );
146 }
147 }
148
149 /**
150 * Validate subscription-related rules
151 *
152 * @since 4.8.0
153 *
154 * @throws DonationValidationException
155 */
156 public function validateSubscriptionRules(): void
157 {
158 // When subscriptionId is greater than zero, type must be "subscription" or "renewal"
159 if ($this->subscriptionId > 0) {
160 if (!$this->type || !in_array($this->type->getValue(), ['subscription', 'renewal'], true)) {
161 throw new DonationValidationException(
162 __('When subscriptionId is provided, type must be "subscription" or "renewal"', 'give'),
163 'invalid_donation_type_for_subscription',
164 400
165 );
166 }
167
168 // Validate subscription exists
169 $subscription = Subscription::find($this->subscriptionId);
170 if (!$subscription) {
171 throw new DonationValidationException(
172 __('Subscription not found', 'give'),
173 'subscription_not_found',
174 404
175 );
176 }
177
178 // When creating a donation associated with subscriptionId, ensure type is not "subscription"
179 // if a donation of that type already exists for this subscription
180 if ($this->type->getValue() === 'subscription' && $subscription->totalDonations() > 0) {
181 throw new DonationValidationException(
182 __('A subscription donation already exists for this subscription', 'give'),
183 'subscription_donation_already_exists',
184 400
185 );
186 }
187
188 // When creating a subscription or renewal donation, ensure gatewayId matches the subscription's gateway
189 if (in_array($this->type->getValue(), ['subscription', 'renewal'], true)) {
190 $donationGatewayId = $this->attributes['gatewayId'] ?? null;
191 if ($donationGatewayId && $subscription->gatewayId && $donationGatewayId !== $subscription->gatewayId) {
192 throw new DonationValidationException(
193 __('Gateway ID must match the subscription gateway for subscription and renewal donations', 'give'),
194 'gateway_mismatch_for_subscription_donation',
195 400
196 );
197 }
198 }
199 } else {
200 // When subscriptionId is zero, type can only be "single" (if provided)
201 if ($this->type && $this->type->getValue() !== 'single') {
202 throw new DonationValidationException(
203 __('When subscriptionId is zero, type can only be "single"', 'give'),
204 'invalid_donation_type_for_single',
205 400
206 );
207 }
208
209 // Set type to single if not provided
210 if (!$this->type) {
211 $this->attributes['type'] = DonationType::SINGLE();
212 }
213 }
214 }
215
216 /**
217 * Convert to Donation model
218 *
219 * @since 4.8.0
220 *
221 * @return Donation
222 * @throws Exception
223 */
224 public function createDonation(): Donation
225 {
226 $this->validateSubscriptionRules();
227 $this->validateCreateDonation();
228
229 // Filter out only the auto-generated id and campaignId fields
230 $donationAttributes = array_filter($this->attributes, function ($key) {
231 return !in_array($key, ['id', 'campaignId'], true);
232 }, ARRAY_FILTER_USE_KEY);
233
234 $donation = Donation::create($donationAttributes);
235
236 return $donation;
237 }
238
239 /**
240 * Convert to renewal donation using subscription
241 *
242 * @since 4.8.0
243 *
244 * @return Donation
245 */
246 public function createRenewal(): Donation
247 {
248 $this->validateSubscriptionRules();
249 $this->validateCreateRenewal();
250
251 $subscription = Subscription::find($this->subscriptionId);
252
253 // Update subscription renewal date if requested BEFORE creating renewal
254 // This ensures the bumpRenewalDate() calculation uses the correct base date
255 if ($this->shouldUpdateRenewalDate()) {
256 $this->updateSubscriptionRenewalDate($subscription);
257 }
258
259 // Pass the processed attributes to allow overriding values from the request
260 // Filter out only the auto-generated id and campaignId fields and subscription-specific fields, allowing createdAt and updatedAt to be set
261 $renewalAttributes = array_filter($this->attributes, function ($key) {
262 return !in_array($key, ['id', 'campaignId','subscriptionId', 'type'], true);
263 }, ARRAY_FILTER_USE_KEY);
264
265 $donation = $subscription->createRenewal($renewalAttributes);
266
267 return $donation;
268 }
269
270 /**
271 * Update subscription renewal date with the createdAt date
272 *
273 * @since 4.8.0
274 *
275 * @param Subscription $subscription
276 * @return void
277 */
278 private function updateSubscriptionRenewalDate(Subscription $subscription): void
279 {
280 if (isset($this->attributes['createdAt']) && $this->attributes['createdAt'] instanceof \DateTime) {
281 $subscription->renewsAt = $this->attributes['createdAt'];
282 $subscription->save();
283 }
284 }
285
286 /**
287 * Get the donation type
288 *
289 * @since 4.8.0
290 *
291 * @return DonationType|null
292 */
293 public function getType(): ?DonationType
294 {
295 return $this->type;
296 }
297
298 /**
299 * Check if this is a renewal donation
300 *
301 * @since 4.8.0
302 *
303 * @return bool
304 */
305 public function isRenewal(): bool
306 {
307 return $this->isRenewal;
308 }
309
310 /**
311 * Check if this is a subscription or renewal donation
312 *
313 * @since 4.8.0
314 *
315 * @return bool
316 */
317 public function isSubscriptionOrRenewal(): bool
318 {
319 return $this->type && in_array($this->type->getValue(), ['subscription', 'renewal'], true);
320 }
321
322 /**
323 * Check if should update renewal date
324 *
325 * @since 4.8.0
326 *
327 * @return bool
328 */
329 public function shouldUpdateRenewalDate(): bool
330 {
331 return $this->updateRenewalDate && $this->isRenewal() && isset($this->attributes['createdAt']);
332 }
333
334 /**
335 * Check if this is a single donation
336 *
337 * @since 4.8.0
338 *
339 * @return bool
340 */
341 public function isSingle(): bool
342 {
343 return $this->type && $this->type->isSingle();
344 }
345
346 /**
347 * Check if this is a subscription donation
348 *
349 * @since 4.8.0
350 *
351 * @return bool
352 */
353 public function isSubscription(): bool
354 {
355 return $this->type && $this->type->isSubscription();
356 }
357
358 /**
359 * Get the subscription ID
360 *
361 * @since 4.8.0
362 *
363 * @return int
364 */
365 public function getSubscriptionId(): int
366 {
367 return $this->subscriptionId;
368 }
369
370 /**
371 * Get the processed attributes
372 *
373 * @since 4.8.0
374 *
375 * @return array
376 */
377 public function getAttributes(): array
378 {
379 return $this->attributes;
380 }
381
382 /**
383 * Process attributes for special data types
384 *
385 * @since 4.8.0
386 *
387 * @param array $attributes
388 * @return array
389 */
390 private function processAttributes(array $attributes): array
391 {
392 $processedAttributes = [];
393
394 foreach ($attributes as $key => $value) {
395 if ($key === 'id' || ! in_array($key, Donation::propertyKeys(), true)) {
396 // Skip id field as it is always auto-generated or not valid for the Donation model
397 continue;
398 }
399
400 $processedValue = DonationFields::processValue($key, $value);
401
402 // Only include properties that are valid for the Donation model
403 if ($processedValue !== null) {
404 $processedAttributes[$key] = $processedValue;
405 }
406 }
407
408 return $processedAttributes;
409 }
410
411 /**
412 * Determine if this is a renewal donation
413 *
414 * @since 4.8.0
415 *
416 * @return bool
417 */
418 private function determineIfRenewal(): bool
419 {
420 return isset($this->attributes['subscriptionId']) &&
421 $this->attributes['subscriptionId'] > 0 &&
422 isset($this->attributes['type']) &&
423 $this->attributes['type']->getValue() === 'renewal';
424 }
425 }
426