PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 2.31.2
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v2.31.2
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 All 281 releases
surecart / app / src / Models / Subscription.php
Subscription.php
511 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SureCart\Models;
4
5 use SureCart\Models\Traits\HasCustomer;
6 use SureCart\Models\Traits\HasPrice;
7 use SureCart\Models\Traits\HasPurchase;
8
9 /**
10 * Subscription model
11 */
12 class Subscription extends Model {
13 use HasCustomer, HasPrice, HasPurchase;
14
15 /**
16 * Rest API endpoint
17 *
18 * @var string
19 */
20 protected $endpoint = 'subscriptions';
21
22 /**
23 * Object name
24 *
25 * @var string
26 */
27 protected $object_name = 'subscription';
28
29 /**
30 * Update the model.
31 *
32 * @param array $attributes Attributes to update.
33 * @return $this|false
34 */
35 protected function update( $attributes = [] ) {
36 // find existing subscription with purchase record.
37 $existing = ( new Subscription() )->with( [ 'purchase' ] )->find( $attributes['id'] ?? $this->attributes['id'] );
38
39 // do the update and also get the purchase record.
40 $this->with( [ 'purchase' ] );
41 $updated = parent::update( $attributes );
42 if ( is_wp_error( $updated ) ) {
43 return $updated;
44 }
45
46 // do the purchase updated event.
47 if ( ! empty( $updated->purchase ) ) {
48 do_action(
49 'surecart/purchase_updated',
50 $updated->purchase,
51 (object) [
52 'data' => (object) [
53 'object' => (object) $updated->purchase->toArray(),
54 'previous_attributes' => (object) array_filter(
55 [
56 // conditionally have the previous product and quantity as the previous attributes.
57 'product' => $updated->purchase->product_id !== $existing->purchase->product_id ? ( $existing->purchase->product_id ?? null ) : null,
58 'quantity' => $updated->purchase->quantity !== $existing->purchase->quantity ? ( $existing->purchase->quantity ?? 1 ) : null,
59 ]
60 ),
61 ],
62 ]
63 );
64 }
65
66 return $this;
67 }
68
69 /**
70 * Cancel a subscription
71 *
72 * @param string $id Model id.
73 * @return $this|\WP_Error
74 */
75 protected function cancel( $id = null ) {
76 if ( $id ) {
77 $this->setAttribute( 'id', $id );
78 }
79
80 if ( $this->fireModelEvent( 'canceling' ) === false ) {
81 return false;
82 }
83
84 if ( empty( $this->attributes['id'] ) ) {
85 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
86 }
87
88 $attributes = $this->attributes;
89 unset( $attributes['id'] );
90
91 $canceled = $this->with(
92 [
93 'purchase',
94 ]
95 )->makeRequest(
96 [
97 'method' => 'PATCH',
98 'query' => $this->query,
99 'body' => [
100 $this->object_name => $attributes,
101 ],
102 ],
103 $this->endpoint . '/' . $this->attributes['id'] . '/cancel/'
104 );
105
106 if ( is_wp_error( $canceled ) ) {
107 return $canceled;
108 }
109
110 $this->resetAttributes();
111
112 $this->fill( $canceled );
113
114 $this->fireModelEvent( 'canceled' );
115
116 // purchase revoked event.
117 if ( ! empty( $this->purchase->revoked ) ) {
118 do_action( 'surecart/purchase_revoked', $this->purchase );
119 }
120
121 return $this;
122 }
123
124 /**
125 * Complete a subscription
126 *
127 * @param string $id Model id.
128 * @return $this|\WP_Error
129 */
130 protected function complete( $id = null ) {
131 if ( $id ) {
132 $this->setAttribute( 'id', $id );
133 }
134
135 if ( $this->fireModelEvent( 'completeing' ) === false ) {
136 return false;
137 }
138
139 if ( empty( $this->attributes['id'] ) ) {
140 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
141 }
142
143 $completed = $this->with(
144 [
145 'purchase',
146 ]
147 )->makeRequest(
148 [
149 'method' => 'PATCH',
150 'query' => $this->query,
151 ],
152 $this->endpoint . '/' . $this->attributes['id'] . '/complete/'
153 );
154
155 if ( is_wp_error( $completed ) ) {
156 return $completed;
157 }
158
159 $this->resetAttributes();
160
161 $this->fill( $completed );
162
163 $this->fireModelEvent( 'completed' );
164
165 return $this;
166 }
167
168 /**
169 * Restore a subscription
170 *
171 * @param string $id Model id.
172 * @return $this|\WP_Error
173 */
174 protected function restore( $id = null ) {
175 if ( $id ) {
176 $this->setAttribute( 'id', $id );
177 }
178
179 if ( $this->fireModelEvent( 'restoring' ) === false ) {
180 return false;
181 }
182
183 if ( empty( $this->attributes['id'] ) ) {
184 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
185 }
186
187 $restored = $this->with( array( 'purchase' ) )
188 ->makeRequest(
189 [
190 'method' => 'PATCH',
191 'query' => $this->query,
192 ],
193 $this->endpoint . '/' . $this->attributes['id'] . '/restore/'
194 );
195
196 if ( is_wp_error( $restored ) ) {
197 return $restored;
198 }
199
200 $this->resetAttributes();
201
202 $this->fill( $restored );
203
204 $this->fireModelEvent( 'restored' );
205
206 // purchase invoked event.
207 if ( ! empty( $this->purchase ) ) {
208 do_action( 'surecart/purchase_invoked', $this->purchase );
209 }
210
211 return $this;
212 }
213
214 /**
215 * Renew a subscription.
216 *
217 * @param string $id Model id.
218 * @return $this|\WP_Error
219 */
220 protected function renew( $id = null ) {
221 if ( $id ) {
222 $this->setAttribute( 'id', $id );
223 }
224
225 if ( $this->fireModelEvent( 'renewing' ) === false ) {
226 return false;
227 }
228
229 if ( empty( $this->attributes['id'] ) ) {
230 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
231 }
232
233 $renewed = \SureCart::request(
234 $this->endpoint . '/' . $this->attributes['id'],
235 [
236 'method' => 'PATCH',
237 'query' => $this->query,
238 'body' => [
239 $this->object_name => [
240 'cancel_at_period_end' => false,
241 ],
242 ],
243 ]
244 );
245
246 if ( is_wp_error( $renewed ) ) {
247 return $renewed;
248 }
249
250 $this->resetAttributes();
251
252 $this->fill( $renewed );
253
254 $this->fireModelEvent( 'renewed' );
255
256 return $this;
257 }
258
259 /**
260 * Preserve a subscription.
261 *
262 * @param string $id Model id.
263 * @return $this|\WP_Error
264 */
265 protected function preserve( $id = null ) {
266 if ( $id ) {
267 $this->setAttribute( 'id', $id );
268 }
269
270 if ( $this->fireModelEvent( 'preserving' ) === false ) {
271 return false;
272 }
273
274 if ( empty( $this->attributes['id'] ) ) {
275 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
276 }
277
278 $preserved = $this->makeRequest(
279 [
280 'method' => 'PATCH',
281 'query' => $this->query,
282 ],
283 $this->endpoint . '/' . $this->attributes['id'] . '/preserve/'
284 );
285
286 if ( is_wp_error( $preserved ) ) {
287 return $preserved;
288 }
289
290 $this->resetAttributes();
291
292 $this->fill( $preserved );
293
294 $this->fireModelEvent( 'preserved' );
295
296 return $this;
297 }
298
299 /**
300 * Preview the upcoming invoice.
301 *
302 * @param string $args Arguments
303 * @return $this|\WP_Error
304 */
305 protected function upcomingPeriod( $args = [] ) {
306 if ( ! empty( $args['id'] ) ) {
307 $this->setAttribute( 'id', $args['id'] );
308 unset( $args['id'] );
309 }
310
311 if ( $this->fireModelEvent( 'previewingUpcomingPeriod' ) === false ) {
312 return false;
313 }
314
315 if ( empty( $this->attributes['id'] ) ) {
316 return new \WP_Error( 'not_saved', 'Please create the subscription' );
317 }
318
319 $upcoming_period = $this->makeRequest(
320 [
321 'method' => 'PATCH',
322 'query' => $this->query,
323 'body' => [
324 $this->object_name => $args,
325 ],
326 ],
327 $this->endpoint . '/' . $this->attributes['id'] . '/upcoming_period/'
328 );
329
330 if ( is_wp_error( $upcoming_period ) ) {
331 return $upcoming_period;
332 }
333
334 $this->resetAttributes();
335
336 $this->fill( $upcoming_period );
337
338 $this->fireModelEvent( 'previewedUpcomingPeriod' );
339
340 return $this;
341 }
342
343 /**
344 * Pay off a subscription
345 *
346 * @param string $id Model id.
347 * @return $this|\WP_Error
348 */
349 protected function payOff( $id = null ) {
350 if ( $id ) {
351 $this->setAttribute( 'id', $id );
352 }
353
354 if ( $this->fireModelEvent( 'payingOff' ) === false ) {
355 return false;
356 }
357
358 if ( empty( $this->attributes['id'] ) ) {
359 return new \WP_Error( 'not_saved', 'Please create the subscription' );
360 }
361
362 $paid_off = $this->makeRequest(
363 [
364 'method' => 'PATCH',
365 'query' => $this->query,
366 ],
367 $this->endpoint . '/' . $this->attributes['id'] . '/pay_off/'
368 );
369
370 if ( is_wp_error( $paid_off ) ) {
371 return $paid_off;
372 }
373
374 $this->resetAttributes();
375
376 $this->fill( $paid_off );
377
378 $this->fireModelEvent( 'paidOff' );
379
380 return $this;
381 }
382
383 /**
384 * Is this subscription a lifetime one?
385 *
386 * @return boolean
387 */
388 protected function isLifetime() {
389 return $this->attributes['id'] && empty( $this->attributes['current_period_end_at'] );
390 }
391
392 /**
393 * Can the user upgrade this subscription?
394 *
395 * @return boolean
396 */
397 protected function canBeSwitched() {
398 return apply_filters( 'surecart/subscription/can_be_changed', $this->checkIfCanBeSwitched(), $this );
399 }
400
401 /**
402 * Can the subscription be changed?
403 *
404 * @return boolean
405 */
406 private function checkIfCanBeSwitched() {
407 // updates are not enabled for the account.
408 if ( empty( \SureCart::account()->portal_protocol->subscription_updates_enabled ) ) {
409 return false;
410 }
411 // already set to canceling.
412 if ( $this->attributes['cancel_at_period_end'] ) {
413 return false;
414 }
415 // can't update canceled, incomplete, or past due subscriptions.
416 if ( in_array( $this->attributes['status'], [ 'canceled', 'incomplete', 'past_due' ] ) ) {
417 return false;
418 }
419 // must not be lifetime.
420 if ( $this->isLifetime() ) {
421 return false;
422 }
423 return true;
424 }
425
426 /**
427 * Can we cancel the subscription?
428 *
429 * @return boolean
430 */
431 public function canBeCanceled() {
432 return apply_filters( 'surecart/subscription/can_be_canceled', $this->checkIfCanBeSwitched(), $this );
433 }
434
435 /**
436 * Should delay subscription cancellation?
437 *
438 * @return boolean
439 */
440 public function shouldDelayCancellation(): bool {
441 $protocol = \SureCart::account()->subscription_protocol;
442
443 if ( ! $protocol->cancel_window_enabled || empty( $protocol->cancel_window_days ) || empty( $this->attributes['current_period_end_at'] ) ) {
444 return false;
445 }
446
447 $cancel_window_days = $protocol->cancel_window_days;
448 $now = ( new \DateTime() )->format( 'Y-m-d' );
449 $end = new \DateTime();
450 $end->setTimestamp( $this->attributes['current_period_end_at'] );
451 $end = $end->modify( "-{$cancel_window_days} days" );
452 $end = $end->format( 'Y-m-d' );
453
454 return $now < $end;
455 }
456
457 /**
458 * Can the subscription be canceled?
459 *
460 * @return boolean
461 */
462 private function checkIfCanBeCanceled() {
463 // updates are not enabled for the account.
464 if ( empty( \SureCart::account()->portal_protocol->subscription_cancellations_enabled ) ) {
465 return false;
466 }
467
468 // can't cancel canceled, incomplete, or past due subscriptions.
469 if ( in_array( $this->attributes['status'], [ 'canceled', 'incomplete', 'past_due' ] ) ) {
470 return false;
471 }
472
473 return true;
474 }
475
476 /**
477 * Can we update the quantity?
478 *
479 * @return boolean
480 */
481 protected function canUpdateQuantity() {
482 return apply_filters( 'surecart/subscription/can_update_quantity', $this->checkIfCanBeSwitched(), $this );
483 }
484
485 /**
486 * Check if we can update the quantity.
487 *
488 * @return boolean
489 */
490 private function checkIfCanUpdateQuantity() {
491 // quantity changes are not enabled for this account.
492 if ( empty( \SureCart::account()->portal_protocol->subscription_quantity_updates_enabled ) ) {
493 return false;
494 }
495 return true;
496 }
497
498 /**
499 * Get stats for the subscription
500 *
501 * @param array $args Array of arguments for the statistics.
502 *
503 * @return \SureCart\Models\Statistic;
504 */
505 protected function stats( $args = [] ) {
506 $stat = new Statistic();
507 return $stat->where( $args )->find( 'subscriptions' );
508 }
509 }
510
511