PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.0.0-beta2
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.0.0-beta2
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 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Models / Subscription.php
surecart / app / src / Models Last commit date
Blocks 1 year ago Concerns 1 year ago Traits 1 year ago AbandonedCheckout.php 2 years ago AbandonedCheckoutProtocol.php 3 years ago Account.php 3 years ago AccountPortalSession.php 4 years ago Activation.php 2 years ago Affiliation.php 2 years ago AffiliationProduct.php 1 year ago AffiliationProtocol.php 2 years ago AffiliationRequest.php 2 years ago ApiToken.php 3 years ago BalanceTransaction.php 4 years ago Brand.php 4 years ago BulkAction.php 2 years ago Bump.php 1 year ago BuyLink.php 2 years ago CancellationAct.php 3 years ago CancellationReason.php 3 years ago Charge.php 4 years ago Checkout.php 1 year ago Click.php 2 years ago Collection.php 2 years ago CommissionStructure.php 2 years ago Component.php 4 years ago Coupon.php 1 year ago Customer.php 2 years ago CustomerLink.php 4 years ago CustomerNotificationProtocol.php 4 years ago DatabaseModel.php 1 year ago Discount.php 1 year ago Download.php 4 years ago Event.php 4 years ago Export.php 2 years ago Form.php 3 years ago Fulfillment.php 3 years ago FulfillmentItem.php 1 year ago GalleryItem.php 1 year ago GalleryItemAttachment.php 1 year ago GalleryItemMedia.php 1 year ago GalleryItemProductMedia.php 1 year ago IncomingWebhook.php 2 years ago Integration.php 2 years ago Invoice.php 4 years ago License.php 2 years ago LineItem.php 1 year ago ManualPaymentMethod.php 3 years ago Media.php 1 year ago Model.php 1 year ago ModelInterface.php 4 years ago Order.php 2 years ago OrderProtocol.php 4 years ago PaymentIntent.php 4 years ago PaymentMethod.php 4 years ago Payout.php 2 years ago PayoutGroup.php 2 years ago Period.php 3 years ago PortalProtocol.php 4 years ago PortalSession.php 4 years ago Price.php 1 year ago Processor.php 3 years ago Product.php 1 year ago ProductCollection.php 1 year ago ProductGroup.php 2 years ago ProductMedia.php 1 year ago Promotion.php 4 years ago ProvisionalAccount.php 2 years ago Purchase.php 2 years ago Referral.php 2 years ago ReferralItem.php 2 years ago Refund.php 4 years ago RegisteredWebhook.php 2 years ago ReturnItem.php 2 years ago ReturnReason.php 2 years ago ReturnRequest.php 2 years ago ShippingMethod.php 3 years ago ShippingProfile.php 3 years ago ShippingProtocol.php 3 years ago ShippingRate.php 3 years ago ShippingZone.php 3 years ago Statistic.php 4 years ago Subscription.php 1 year ago SubscriptionProtocol.php 4 years ago TaxOverride.php 2 years ago TaxProtocol.php 4 years ago TaxRegistration.php 4 years ago TaxZone.php 4 years ago Upload.php 4 years ago Upsell.php 1 year ago UpsellFunnel.php 2 years ago User.php 2 years ago Variant.php 1 year ago VariantOption.php 2 years ago VariantOptionValue.php 1 year ago VariantValue.php 2 years ago VerificationCode.php 3 years ago Webhook.php 2 years ago WebhookRegistration.php 2 years ago
Subscription.php
519 lines
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 * Set the current period attribute
31 *
32 * @param object $value Return request properties.
33 * @return void
34 */
35 public function setCurrentPeriodAttribute( $value ) {
36 $this->setRelation( 'current_period', $value, Period::class );
37 }
38
39 /**
40 * Update the model.
41 *
42 * @param array $attributes Attributes to update.
43 * @return $this|false
44 */
45 protected function update( $attributes = [] ) {
46 // find existing subscription with purchase record.
47 $existing = ( new Subscription() )->with( [ 'purchase' ] )->find( $attributes['id'] ?? $this->attributes['id'] );
48
49 // do the update and also get the purchase record.
50 $this->with( [ 'purchase' ] );
51 $updated = parent::update( $attributes );
52 if ( is_wp_error( $updated ) ) {
53 return $updated;
54 }
55
56 // do the purchase updated event.
57 if ( ! empty( $updated->purchase ) ) {
58 do_action(
59 'surecart/purchase_updated',
60 $updated->purchase,
61 (object) [
62 'data' => (object) [
63 'object' => (object) $updated->purchase->toArray(),
64 'previous_attributes' => (object) array_filter(
65 [
66 // conditionally have the previous product and quantity as the previous attributes.
67 'product' => $updated->purchase->product_id !== $existing->purchase->product_id ? ( $existing->purchase->product_id ?? null ) : null,
68 'quantity' => $updated->purchase->quantity !== $existing->purchase->quantity ? ( $existing->purchase->quantity ?? 1 ) : null,
69 ]
70 ),
71 ],
72 ]
73 );
74 }
75
76 return $this;
77 }
78
79 /**
80 * Cancel a subscription
81 *
82 * @param string $id Model id.
83 * @return $this|\WP_Error
84 */
85 protected function cancel( $id = null ) {
86 if ( $id ) {
87 $this->setAttribute( 'id', $id );
88 }
89
90 if ( $this->fireModelEvent( 'canceling' ) === false ) {
91 return false;
92 }
93
94 if ( empty( $this->attributes['id'] ) ) {
95 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
96 }
97
98 $attributes = $this->attributes;
99 unset( $attributes['id'] );
100
101 $canceled = $this->with(
102 [
103 'purchase',
104 ]
105 )->makeRequest(
106 [
107 'method' => 'PATCH',
108 'query' => $this->query,
109 'body' => [
110 $this->object_name => $attributes,
111 ],
112 ],
113 $this->endpoint . '/' . $this->attributes['id'] . '/cancel/'
114 );
115
116 if ( is_wp_error( $canceled ) ) {
117 return $canceled;
118 }
119
120 $this->resetAttributes();
121
122 $this->fill( $canceled );
123
124 $this->fireModelEvent( 'canceled' );
125
126 // purchase revoked event.
127 if ( ! empty( $this->purchase->revoked ) ) {
128 do_action( 'surecart/purchase_revoked', $this->purchase );
129 }
130
131 return $this;
132 }
133
134 /**
135 * Complete a subscription
136 *
137 * @param string $id Model id.
138 * @return $this|\WP_Error
139 */
140 protected function complete( $id = null ) {
141 if ( $id ) {
142 $this->setAttribute( 'id', $id );
143 }
144
145 if ( $this->fireModelEvent( 'completeing' ) === false ) {
146 return false;
147 }
148
149 if ( empty( $this->attributes['id'] ) ) {
150 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
151 }
152
153 $completed = $this->with(
154 [
155 'purchase',
156 ]
157 )->makeRequest(
158 [
159 'method' => 'PATCH',
160 'query' => $this->query,
161 ],
162 $this->endpoint . '/' . $this->attributes['id'] . '/complete/'
163 );
164
165 if ( is_wp_error( $completed ) ) {
166 return $completed;
167 }
168
169 $this->resetAttributes();
170
171 $this->fill( $completed );
172
173 $this->fireModelEvent( 'completed' );
174
175 return $this;
176 }
177
178 /**
179 * Restore a subscription
180 *
181 * @param string $id Model id.
182 * @return $this|\WP_Error
183 */
184 protected function restore( $id = null ) {
185 if ( $id ) {
186 $this->setAttribute( 'id', $id );
187 }
188
189 if ( $this->fireModelEvent( 'restoring' ) === false ) {
190 return false;
191 }
192
193 if ( empty( $this->attributes['id'] ) ) {
194 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
195 }
196
197 $restored = $this->with( array( 'purchase' ) )
198 ->makeRequest(
199 [
200 'method' => 'PATCH',
201 'query' => $this->query,
202 ],
203 $this->endpoint . '/' . $this->attributes['id'] . '/restore/'
204 );
205
206 if ( is_wp_error( $restored ) ) {
207 return $restored;
208 }
209
210 $this->resetAttributes();
211
212 $this->fill( $restored );
213
214 $this->fireModelEvent( 'restored' );
215
216 // purchase invoked event.
217 if ( ! empty( $this->purchase ) ) {
218 do_action( 'surecart/purchase_invoked', $this->purchase );
219 }
220
221 return $this;
222 }
223
224 /**
225 * Renew a subscription.
226 *
227 * @param string $id Model id.
228 * @return $this|\WP_Error
229 */
230 protected function renew( $id = null ) {
231 if ( $id ) {
232 $this->setAttribute( 'id', $id );
233 }
234
235 if ( $this->fireModelEvent( 'renewing' ) === false ) {
236 return false;
237 }
238
239 if ( empty( $this->attributes['id'] ) ) {
240 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
241 }
242
243 $renewed = \SureCart::request(
244 $this->endpoint . '/' . $this->attributes['id'],
245 [
246 'method' => 'PATCH',
247 'query' => $this->query,
248 'body' => [
249 $this->object_name => [
250 'cancel_at_period_end' => false,
251 ],
252 ],
253 ]
254 );
255
256 if ( is_wp_error( $renewed ) ) {
257 return $renewed;
258 }
259
260 $this->resetAttributes();
261
262 $this->fill( $renewed );
263
264 $this->fireModelEvent( 'renewed' );
265
266 return $this;
267 }
268
269 /**
270 * Preserve a subscription.
271 *
272 * @param string $id Model id.
273 * @return $this|\WP_Error
274 */
275 protected function preserve( $id = null ) {
276 if ( $id ) {
277 $this->setAttribute( 'id', $id );
278 }
279
280 if ( $this->fireModelEvent( 'preserving' ) === false ) {
281 return false;
282 }
283
284 if ( empty( $this->attributes['id'] ) ) {
285 return new \WP_Error( 'not_saved', 'Please create the subscription.' );
286 }
287
288 $preserved = $this->makeRequest(
289 [
290 'method' => 'PATCH',
291 'query' => $this->query,
292 ],
293 $this->endpoint . '/' . $this->attributes['id'] . '/preserve/'
294 );
295
296 if ( is_wp_error( $preserved ) ) {
297 return $preserved;
298 }
299
300 $this->resetAttributes();
301
302 $this->fill( $preserved );
303
304 $this->fireModelEvent( 'preserved' );
305
306 return $this;
307 }
308
309 /**
310 * Preview the upcoming invoice.
311 *
312 * @param string $args Arguments
313 * @return $this|\WP_Error
314 */
315 protected function upcomingPeriod( $args = [] ) {
316 if ( ! empty( $args['id'] ) ) {
317 $this->setAttribute( 'id', $args['id'] );
318 unset( $args['id'] );
319 }
320
321 if ( $this->fireModelEvent( 'previewingUpcomingPeriod' ) === false ) {
322 return false;
323 }
324
325 if ( empty( $this->attributes['id'] ) ) {
326 return new \WP_Error( 'not_saved', 'Please create the subscription' );
327 }
328
329 $upcoming_period = $this->makeRequest(
330 [
331 'method' => 'PATCH',
332 'query' => $this->query,
333 'body' => [
334 $this->object_name => $args,
335 ],
336 ],
337 $this->endpoint . '/' . $this->attributes['id'] . '/upcoming_period/'
338 );
339
340 if ( is_wp_error( $upcoming_period ) ) {
341 return $upcoming_period;
342 }
343
344 $period = new Period( $upcoming_period );
345
346 $this->fireModelEvent( 'previewedUpcomingPeriod' );
347
348 return $period;
349 }
350
351 /**
352 * Pay off a subscription
353 *
354 * @param string $id Model id.
355 * @return $this|\WP_Error
356 */
357 protected function payOff( $id = null ) {
358 if ( $id ) {
359 $this->setAttribute( 'id', $id );
360 }
361
362 if ( $this->fireModelEvent( 'payingOff' ) === false ) {
363 return false;
364 }
365
366 if ( empty( $this->attributes['id'] ) ) {
367 return new \WP_Error( 'not_saved', 'Please create the subscription' );
368 }
369
370 $paid_off = $this->makeRequest(
371 [
372 'method' => 'PATCH',
373 'query' => $this->query,
374 ],
375 $this->endpoint . '/' . $this->attributes['id'] . '/pay_off/'
376 );
377
378 if ( is_wp_error( $paid_off ) ) {
379 return $paid_off;
380 }
381
382 $this->resetAttributes();
383
384 $this->fill( $paid_off );
385
386 $this->fireModelEvent( 'paidOff' );
387
388 return $this;
389 }
390
391 /**
392 * Is this subscription a lifetime one?
393 *
394 * @return boolean
395 */
396 protected function isLifetime() {
397 return $this->attributes['id'] && empty( $this->attributes['current_period_end_at'] );
398 }
399
400 /**
401 * Can the user upgrade this subscription?
402 *
403 * @return boolean
404 */
405 protected function canBeSwitched() {
406 return apply_filters( 'surecart/subscription/can_be_changed', $this->checkIfCanBeSwitched(), $this );
407 }
408
409 /**
410 * Can the subscription be changed?
411 *
412 * @return boolean
413 */
414 private function checkIfCanBeSwitched() {
415 // updates are not enabled for the account.
416 if ( empty( \SureCart::account()->portal_protocol->subscription_updates_enabled ) ) {
417 return false;
418 }
419 // already set to canceling.
420 if ( $this->attributes['cancel_at_period_end'] ) {
421 return false;
422 }
423 // can't update canceled, incomplete, or past due subscriptions.
424 if ( in_array( $this->attributes['status'], [ 'canceled', 'incomplete', 'past_due' ] ) ) {
425 return false;
426 }
427 // must not be lifetime.
428 if ( $this->isLifetime() ) {
429 return false;
430 }
431 return true;
432 }
433
434 /**
435 * Can we cancel the subscription?
436 *
437 * @return boolean
438 */
439 public function canBeCanceled() {
440 return apply_filters( 'surecart/subscription/can_be_canceled', $this->checkIfCanBeSwitched(), $this );
441 }
442
443 /**
444 * Should delay subscription cancellation?
445 *
446 * @return boolean
447 */
448 public function shouldDelayCancellation(): bool {
449 $protocol = \SureCart::account()->subscription_protocol;
450
451 if ( ! $protocol->cancel_window_enabled || empty( $protocol->cancel_window_days ) || empty( $this->attributes['current_period_end_at'] ) ) {
452 return false;
453 }
454
455 $cancel_window_days = $protocol->cancel_window_days;
456 $now = ( new \DateTime() )->format( 'Y-m-d' );
457 $end = new \DateTime();
458 $end->setTimestamp( $this->attributes['current_period_end_at'] );
459 $end = $end->modify( "-{$cancel_window_days} days" );
460 $end = $end->format( 'Y-m-d' );
461
462 return $now < $end;
463 }
464
465 /**
466 * Can the subscription be canceled?
467 *
468 * @return boolean
469 */
470 private function checkIfCanBeCanceled() {
471 // updates are not enabled for the account.
472 if ( empty( \SureCart::account()->portal_protocol->subscription_cancellations_enabled ) ) {
473 return false;
474 }
475
476 // can't cancel canceled, incomplete, or past due subscriptions.
477 if ( in_array( $this->attributes['status'], [ 'canceled', 'incomplete', 'past_due' ] ) ) {
478 return false;
479 }
480
481 return true;
482 }
483
484 /**
485 * Can we update the quantity?
486 *
487 * @return boolean
488 */
489 protected function canUpdateQuantity() {
490 return apply_filters( 'surecart/subscription/can_update_quantity', $this->checkIfCanBeSwitched(), $this );
491 }
492
493 /**
494 * Check if we can update the quantity.
495 *
496 * @return boolean
497 */
498 private function checkIfCanUpdateQuantity() {
499 // quantity changes are not enabled for this account.
500 if ( empty( \SureCart::account()->portal_protocol->subscription_quantity_updates_enabled ) ) {
501 return false;
502 }
503 return true;
504 }
505
506 /**
507 * Get stats for the subscription
508 *
509 * @param array $args Array of arguments for the statistics.
510 *
511 * @return \SureCart\Models\Statistic;
512 */
513 protected function stats( $args = [] ) {
514 $stat = new Statistic();
515 return $stat->where( $args )->find( 'subscriptions' );
516 }
517 }
518
519