PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.5.0
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.5.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 / Checkout.php
surecart / app / src / Models Last commit date
Blocks 2 months ago Concerns 6 months ago Traits 2 weeks ago Wordpress 1 year ago AbandonedCheckout.php 1 year ago AbandonedCheckoutProtocol.php 3 years ago Account.php 10 months ago AccountPortalSession.php 3 years ago Activation.php 1 year ago Affiliation.php 1 year ago AffiliationProduct.php 1 year ago AffiliationProtocol.php 2 years ago AffiliationRequest.php 1 year ago ApiToken.php 6 months ago AutoFee.php 5 months ago AutoFeeProtocol.php 5 months ago BalanceTransaction.php 1 year ago Batch.php 5 days ago Brand.php 4 months ago BulkAction.php 2 years ago Bump.php 1 year ago BuyLink.php 2 years ago CancellationAct.php 1 year ago CancellationReason.php 3 years ago Charge.php 9 months ago Checkout.php 2 months ago Click.php 1 year ago Collection.php 3 months ago CommissionStructure.php 2 years ago Component.php 4 months ago Coupon.php 2 months ago Customer.php 2 weeks ago CustomerLink.php 3 years ago CustomerNotificationProtocol.php 3 years ago CustomerPortalProtocol.php 1 year ago DatabaseModel.php 2 months ago Discount.php 1 year ago DisplayCurrency.php 1 year ago Dispute.php 9 months ago Download.php 3 years ago Event.php 3 years ago Export.php 2 years ago ExternalApiModel.php 3 months ago Fee.php 1 year ago Form.php 3 years ago Fulfillment.php 1 year ago FulfillmentItem.php 1 year ago GalleryItem.php 9 months ago GalleryItemAttachment.php 5 months ago GalleryItemImageAttachment.php 3 months ago GalleryItemMedia.php 1 year ago GalleryItemProductMedia.php 1 month ago GalleryItemVideoAttachment.php 3 months ago Import.php 6 months ago ImportRow.php 2 months ago IncomingWebhook.php 1 year ago Integration.php 2 years ago IntegrationCatalog.php 5 days ago Invoice.php 1 year ago License.php 1 year ago LineItem.php 9 months ago ManualPaymentMethod.php 1 year ago Media.php 1 month ago Model.php 5 days ago ModelInterface.php 3 years ago Order.php 2 weeks ago OrderProtocol.php 3 years ago ParcelTemplate.php 3 months ago PaymentFailure.php 1 year ago PaymentInstrument.php 2 months ago PaymentIntent.php 1 year ago PaymentMethod.php 1 year ago Payout.php 1 year ago PayoutGroup.php 2 years ago Period.php 1 year ago PlatformFee.php 1 year ago PortalSession.php 3 years ago Price.php 7 months ago Processor.php 1 year ago Product.php 5 days ago ProductCollection.php 1 year ago ProductCollectionImport.php 6 months ago ProductGroup.php 1 year ago ProductImport.php 6 months ago ProductMedia.php 4 months ago Promotion.php 1 year ago ProvisionalAccount.php 2 months ago Purchase.php 2 years ago Referral.php 1 year ago ReferralItem.php 2 years ago Refund.php 1 year ago RefundItem.php 1 year ago RegisteredWebhook.php 2 months ago ReturnItem.php 2 years ago ReturnReason.php 2 years ago ReturnRequest.php 1 year ago Review.php 4 months ago ReviewProtocol.php 4 months ago RuleSchema.php 5 months ago ServiceFee.php 1 year ago ShippingChoice.php 1 year 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 3 years ago Subscription.php 2 weeks ago SubscriptionProtocol.php 3 years ago Swap.php 1 year ago TaxOverride.php 2 years ago TaxProtocol.php 3 years ago TaxRegistration.php 1 year ago TaxZone.php 3 years ago Upload.php 3 years ago Upsell.php 1 year ago UpsellFunnel.php 1 year ago User.php 1 month ago Variant.php 5 days ago VariantOption.php 2 years ago VariantOptionValue.php 1 year ago VariantValue.php 2 years ago VerificationCode.php 3 years ago Webhook.php 1 year ago WebhookRegistration.php 2 years ago
Checkout.php
799 lines
1 <?php
2
3 namespace SureCart\Models;
4
5 use SureCart\Models\Traits\HasCustomer;
6 use SureCart\Models\Traits\HasSubscriptions;
7 use SureCart\Models\LineItem;
8 use SureCart\Models\Fee;
9 use SureCart\Models\Traits\CanFinalize;
10 use SureCart\Models\Traits\HasBillingAddress;
11 use SureCart\Models\Traits\HasDiscount;
12 use SureCart\Models\Traits\HasInvoice;
13 use SureCart\Models\Traits\HasPaymentFailures;
14 use SureCart\Models\Traits\HasPaymentIntent;
15 use SureCart\Models\Traits\HasPaymentMethod;
16 use SureCart\Models\Traits\HasProcessorType;
17 use SureCart\Models\Traits\HasPurchases;
18 use SureCart\Models\Traits\HasShippingAddress;
19 use SureCart\Models\Traits\HasShippingChoices;
20 use SureCart\Support\Currency;
21 use SureCart\Support\TimeDate;
22
23 /**
24 * Order model
25 */
26 class Checkout extends Model {
27 use HasCustomer;
28 use HasSubscriptions;
29 use HasDiscount;
30 use HasShippingAddress;
31 use HasShippingChoices;
32 use HasPaymentIntent;
33 use HasPaymentMethod;
34 use HasPurchases;
35 use CanFinalize;
36 use HasProcessorType;
37 use HasBillingAddress;
38 use HasPaymentFailures;
39 use HasInvoice;
40
41 /**
42 * Rest API endpoint
43 *
44 * @var string
45 */
46 protected $endpoint = 'checkouts';
47
48 /**
49 * Object name
50 *
51 * @var string
52 */
53 protected $object_name = 'checkout';
54
55 /**
56 * Need to pass the processor type on create
57 *
58 * @param array $attributes Optional attributes.
59 * @param string $type stripe, paypal, etc.
60 */
61 public function __construct( $attributes = [], $type = '' ) {
62 $this->processor_type = $type;
63 parent::__construct( $attributes );
64 }
65
66 /**
67 * Get the display subtotal amount attribute.
68 *
69 * @return string
70 */
71 public function getAmountDueDisplayAmountAttribute() {
72 return Currency::format( $this->amount_due ?? 0, $this->currency );
73 }
74
75 /**
76 * Get the display subtotal amount attribute.
77 *
78 * @return string
79 */
80 public function getSubtotalDisplayAmountAttribute() {
81 return ! empty( $this->subtotal_amount ) ? Currency::format( $this->subtotal_amount, $this->currency ) : '';
82 }
83
84 /**
85 * Get the display discounts amount attribute.
86 *
87 * @return string
88 */
89 public function getDiscountsDisplayAmountAttribute() {
90 return ! empty( $this->discount_amount ) ? Currency::format( $this->discount_amount, $this->currency ) : '';
91 }
92
93 /**
94 * Get the display tax reverse charged amount attribute.
95 *
96 * @return string
97 */
98 public function getTaxReverseChargedDisplayAmountAttribute() {
99 return ! empty( $this->tax_reverse_charged_amount ) ? Currency::format( $this->tax_reverse_charged_amount, $this->currency ) : '';
100 }
101
102 /**
103 * Get the display tax reverse charged amount attribute.
104 *
105 * @return string
106 */
107 public function getDiscountsDisplayAttribute() {
108 return ! empty( $this->discounts ) ? Currency::format( $this->discounts, $this->currency ) : '';
109 }
110
111 /**
112 * Get the display scratch amount attribute.
113 *
114 * @return string
115 */
116 public function getScratchDisplayAmountAttribute() {
117 return Currency::format( (int) ( -$this->total_savings_amount + $this->total_amount ), $this->currency );
118 }
119
120 /**
121 * Get the display subtotal amount attribute.
122 *
123 * @return string
124 */
125 public function getTrialDisplayAmountAttribute() {
126 return ! empty( $this->trial_amount ) ? Currency::format( $this->trial_amount, $this->currency ) : '';
127 }
128
129 /**
130 * Get the display remaining amount due attribute.
131 *
132 * @return string
133 */
134 public function getRemainingAmountDueDisplayAmountAttribute() {
135 return ! empty( $this->remaining_amount_due ) ? Currency::format( $this->remaining_amount_due, $this->currency ) : '';
136 }
137
138 /**
139 * Get the display refunded amount attribute.
140 *
141 * @return string
142 */
143 public function getRefundedDisplayAmountAttribute() {
144 return ! empty( $this->refunded_amount ) ? Currency::format( $this->refunded_amount, $this->currency ) : '';
145 }
146
147 /**
148 * Get the display net paid amount attribute.
149 *
150 * @return string
151 */
152 public function getNetPaidDisplayAmountAttribute() {
153 return ! empty( $this->net_paid_amount ) ? Currency::format( $this->net_paid_amount, $this->currency ) : '';
154 }
155
156 /**
157 * Get the display subtotal amount attribute.
158 *
159 * @return string
160 */
161 public function getTotalDisplayAmountAttribute() {
162 return Currency::format( (int) $this->total_amount, $this->currency );
163 }
164
165 /**
166 * Get the display total savings amount attribute.
167 *
168 * @return string
169 */
170 public function getTotalSavingsDisplayAmountAttribute() {
171 return Currency::format( (int) $this->total_savings_amount, $this->currency );
172 }
173
174 /**
175 * Get the display total scratch price attribute.
176 *
177 * @return string
178 */
179 public function getTotalScratchDisplayAmountAttribute() {
180 return Currency::format( - (int) $this->total_savings_amount + (int) $this->total_amount, $this->currency );
181 }
182
183 /**
184 * Get the subtotal scratch amount (sum of all line items' scratch amounts).
185 *
186 * @return int
187 */
188 public function getSubtotalScratchAmountAttribute() {
189 if ( empty( $this->line_items ) || empty( $this->line_items->data ) ) {
190 return 0;
191 }
192 return array_reduce(
193 $this->line_items->data ?? [],
194 function ( $sum, $item ) {
195 return $sum + (int) ( $item->scratch_amount ?? 0 );
196 },
197 0
198 );
199 }
200
201 /**
202 * Get the display subtotal scratch amount attribute.
203 *
204 * @return string
205 */
206 public function getSubtotalScratchDisplayAmountAttribute() {
207 $scratch = $this->subtotal_scratch_amount;
208 return ! empty( $scratch ) ? Currency::format( $scratch, $this->currency ) : '';
209 }
210
211 /**
212 * Check if the checkout has a subtotal scratch amount different from the subtotal.
213 *
214 * @return bool
215 */
216 public function getHasSubtotalScratchAmountAttribute() {
217 $scratch = $this->subtotal_scratch_amount;
218 $subtotal = (int) ( $this->subtotal_amount ?? 0 );
219 return $scratch > 0 && $scratch > $subtotal;
220 }
221
222 /**
223 * Get the converts currency attribute.
224 * We should convert currency by default.
225 *
226 * @return bool
227 */
228 public function getShowConvertedTotalAttribute() {
229 return apply_filters( 'surecart_checkout_show_converted_total', true, $this );
230 }
231
232 /**
233 * Get the display full amount attribute.
234 *
235 * @return string
236 */
237 public function getFullDisplayAmountAttribute() {
238 return ! empty( $this->full_amount ) ? Currency::format( $this->full_amount, $this->currency ) : '';
239 }
240
241 /**
242 * Get the display discount amount amount attribute.
243 *
244 * @return string
245 */
246 public function getDiscountDisplayAmountAttribute() {
247 return ! empty( $this->discount_amount ) ? Currency::format( $this->discount_amount, $this->currency ) : '';
248 }
249
250 /**
251 * Get the display bump amount attribute.
252 *
253 * @return string
254 */
255 public function getBumpDisplayAmountAttribute() {
256 return ! empty( $this->bump_amount ) ? Currency::format( $this->bump_amount, $this->currency ) : '';
257 }
258
259 /**
260 * Get the converted total amount attribute.
261 *
262 * @return string
263 */
264 public function getConvertedTotalAmountAttribute() {
265 if ( $this->is_zero_decimal || empty( $this->total_amount ) ) {
266 return $this->total_amount;
267 }
268 return $this->total_amount / 100;
269 }
270
271 /**
272 * Is the checkout an installment.
273 *
274 * @return bool
275 */
276 public function getIsInstallmentAttribute() {
277 return $this->full_amount !== $this->subtotal_amount;
278 }
279
280 /**
281 * Get the line items count attribute.
282 *
283 * @return int
284 */
285 public function getLineItemsCountAttribute() {
286 if ( empty( $this->line_items ) || empty( $this->line_items->data ) ) {
287 return 0;
288 }
289 return array_reduce(
290 $this->line_items->data ?? [],
291 function ( $count, $item ) {
292 return $count + ( $item->quantity ?? 0 );
293 },
294 0
295 );
296 }
297
298 /**
299 * Get the has recurring attribute.
300 *
301 * Do any line items have a recurring price?
302 *
303 * @return bool
304 */
305 public function getHasRecurringAttribute() {
306 return array_reduce(
307 $this->line_items->data ?? [],
308 function ( $carry, $item ) {
309 return $carry || isset( $item->price->recurring_interval );
310 },
311 false
312 );
313 }
314
315 /**
316 * Set attributes during write actions.
317 *
318 * @return void
319 */
320 protected function setWriteAttributes() {
321 $this->setAttribute( 'ip_address', $this->getIPAddress() );
322 if ( isset( $_COOKIE['sc_click_id'] ) ) {
323 $this->setAttribute( 'last_click', $_COOKIE['sc_click_id'] );
324 }
325 }
326
327 /**
328 * Set the upsell funnel attribute
329 *
330 * @param object $value The data array.
331 * @return void
332 */
333 public function setUpsellFunnelAttribute( $value ) {
334 $this->setRelation( 'upsell_funnel', $value, UpsellFunnel::class );
335 }
336
337 /**
338 * Set the upsell funnel attribute
339 *
340 * @param object $value The data array.
341 * @return void
342 */
343 public function setCurrentUpsellAttribute( $value ) {
344 $this->setRelation( 'current_upsell', $value, Upsell::class );
345 }
346
347 /**
348 * Set the recommended bumps attribute
349 *
350 * @param object $value Subscription data array.
351 * @return void
352 */
353 public function setRecommendedBumpsAttribute( $value ) {
354 $this->setCollection( 'recommended_bumps', $value, Bump::class );
355 }
356
357 /**
358 * Set the checkout fees attribute
359 *
360 * @param object $value Subscription data array.
361 * @return void
362 */
363 public function setCheckoutFeesAttribute( $value ) {
364 $this->setCollection( 'checkout_fees', $value, Fee::class );
365 }
366
367 /**
368 * Set the shipping fees attribute
369 *
370 * @param object $value Subscription data array.
371 * @return void
372 */
373 public function setShippingFeesAttribute( $value ) {
374 $this->setCollection( 'shipping_fees', $value, Fee::class );
375 }
376
377 /**
378 * Get the display checkout fees amount attribute.
379 *
380 * @return string
381 */
382 public function getCheckoutFeesDisplayAmountAttribute() {
383 return ! empty( $this->checkout_fees_amount ) ? Currency::format( $this->checkout_fees_amount, $this->currency ) : '';
384 }
385
386 /**
387 * Get the display shipping fees amount attribute.
388 *
389 * @return string
390 */
391 public function getShippingFeesDisplayAmountAttribute() {
392 return ! empty( $this->shipping_fees_amount ) ? Currency::format( $this->shipping_fees_amount, $this->currency ) : '';
393 }
394
395 /**
396 * Create a new model
397 *
398 * @param array $attributes Attributes to create.
399 *
400 * @return $this|\WP_Error|false
401 */
402 protected function create( $attributes = [] ) {
403 $this->setWriteAttributes();
404 return parent::create( $attributes );
405 }
406
407 /**
408 * Update the model
409 *
410 * @param array $attributes Attributes to create.
411 *
412 * @return $this|\WP_Error|false
413 */
414 protected function update( $attributes = [] ) {
415 $this->setWriteAttributes();
416
417 return parent::update( $attributes );
418 }
419
420 /**
421 * Get the IP address of the user
422 *
423 * TOD0: Move this to a helper class.
424 *
425 * @return string
426 */
427 protected function getIPAddress() {
428 return $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
429 }
430
431 /**
432 * Set the product attribute
433 *
434 * @param object $value Product properties.
435 * @return void
436 */
437 public function setLineItemsAttribute( $value ) {
438 $this->setCollection( 'line_items', $value, LineItem::class );
439 }
440
441 /**
442 * Finalize the session for checkout.
443 *
444 * @return $this|\WP_Error
445 */
446 protected function manuallyPay() {
447 if ( $this->fireModelEvent( 'manually_paying' ) === false ) {
448 return false;
449 }
450
451 if ( empty( $this->attributes['id'] ) ) {
452 return new \WP_Error( 'not_saved', 'Please create the checkout session.' );
453 }
454
455 $finalized = \SureCart::request(
456 $this->endpoint . '/' . $this->attributes['id'] . '/manually_pay/',
457 [
458 'method' => 'PATCH',
459 'query' => $this->query,
460 'body' => [
461 $this->object_name => $this->getAttributes(),
462 ],
463 ]
464 );
465
466 if ( is_wp_error( $finalized ) ) {
467 return $finalized;
468 }
469
470 $this->resetAttributes();
471
472 $this->fill( $finalized );
473
474 $this->fireModelEvent( 'manually_paid' );
475
476 return $this;
477 }
478
479 /**
480 * Cancel an checkout
481 *
482 * @return $this|\WP_Error
483 */
484 protected function cancel() {
485 if ( $this->fireModelEvent( 'cancelling' ) === false ) {
486 return false;
487 }
488
489 if ( empty( $this->attributes['id'] ) ) {
490 return new \WP_Error( 'not_saved', 'Please create the order.' );
491 }
492
493 $cancelled = $this->makeRequest(
494 [
495 'method' => 'PATCH',
496 'query' => $this->query,
497 'body' => [
498 $this->object_name => $this->getAttributes(),
499 ],
500 ],
501 $this->endpoint . '/' . $this->attributes['id'] . '/cancel/'
502 );
503
504 if ( is_wp_error( $cancelled ) ) {
505 return $cancelled;
506 }
507
508 $this->resetAttributes();
509
510 $this->fill( $cancelled );
511
512 $this->fireModelEvent( 'cancelled' );
513
514 return $this;
515 }
516
517 /**
518 * Offer a bump.
519 *
520 * @param string|\SureCart\Models\Bump $bump The bump object.
521 *
522 * @return true|\WP_Error
523 */
524 protected function offerBump( $bump ) {
525 if ( $this->fireModelEvent( 'offering_bump' ) === false ) {
526 return false;
527 }
528
529 if ( empty( $this->attributes['id'] ) ) {
530 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
531 }
532
533 $id = is_a( $bump, Bump::class ) ? $bump->id : $bump;
534
535 if ( empty( $id ) ) {
536 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
537 }
538
539 $offered = $this->makeRequest(
540 [
541 'method' => 'PATCH',
542 'query' => $this->query,
543 'body' => [
544 $this->object_name => $this->getAttributes(),
545 ],
546 ],
547 $this->endpoint . '/' . $this->attributes['id'] . '/offer_bump/' . $id
548 );
549
550 if ( is_wp_error( $offered ) ) {
551 return $offered;
552 }
553
554 return true;
555 }
556
557 /**
558 * Offer an upsell.
559 *
560 * @param string|\SureCart\Models\Upsell $upsell The upsell object.
561 *
562 * @return true|\WP_Error
563 */
564 protected function offerUpsell( $upsell ) {
565 if ( $this->fireModelEvent( 'offering_upsell' ) === false ) {
566 return false;
567 }
568
569 if ( empty( $this->attributes['id'] ) ) {
570 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
571 }
572
573 $id = is_a( $upsell, Upsell::class ) ? $upsell->id : $upsell;
574
575 if ( empty( $id ) ) {
576 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
577 }
578
579 $offered = $this->makeRequest(
580 [
581 'method' => 'PATCH',
582 'query' => $this->query,
583 'body' => [
584 $this->object_name => $this->getAttributes(),
585 ],
586 ],
587 $this->endpoint . '/' . $this->attributes['id'] . '/offer_upsell/' . $id
588 );
589
590 if ( is_wp_error( $offered ) ) {
591 return $offered;
592 }
593
594 return true;
595 }
596
597 /**
598 * Decline an upsell.
599 *
600 * @param string|\SureCart\Models\Upsell $upsell The upsell object.
601 *
602 * @return $this|\WP_Error
603 */
604 protected function declineUpsell( $upsell ) {
605 if ( $this->fireModelEvent( 'declining_upsell' ) === false ) {
606 return false;
607 }
608
609 if ( empty( $this->attributes['id'] ) ) {
610 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
611 }
612
613 $id = is_a( $upsell, Upsell::class ) ? $upsell->id : $upsell;
614
615 if ( empty( $id ) ) {
616 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
617 }
618
619 $declined = $this->makeRequest(
620 [
621 'method' => 'PATCH',
622 'query' => $this->query,
623 'body' => [
624 $this->object_name => $this->getAttributes(),
625 ],
626 ],
627 $this->endpoint . '/' . $this->attributes['id'] . '/decline_upsell/' . $id
628 );
629
630 if ( is_wp_error( $declined ) ) {
631 return $declined;
632 }
633
634 $this->resetAttributes();
635
636 $this->fill( $declined );
637
638 $this->fireModelEvent( 'declined' );
639
640 return $this;
641 }
642
643 /**
644 * Get the human discount attribute.
645 *
646 * @return string
647 */
648 public function getHumanDiscountAttribute() {
649 if ( empty( $this->discount->coupon ) ) {
650 return '';
651 }
652
653 if ( ! empty( $this->discount->coupon->percent_off ) ) {
654 if ( ! empty( $this->discount->coupon->amount_off ) && ! empty( $this->currency ) ) {
655 return Currency::format( $this->discount->coupon->amount_off, $this->currency );
656 }
657
658 $percent = sprintf( '%d%%', (int) $this->discount->coupon->percent_off );
659 // translators: %s is the discount percentage (e.g. "10%").
660 return sprintf( __( '%s off', 'surecart' ), $percent );
661 }
662
663 return '';
664 }
665
666 /**
667 * Get the human discount with duration attribute.
668 *
669 * @return string
670 */
671 public function getHumanDiscountWithDurationAttribute() {
672 if ( ! $this->hasRecurring ) {
673 return $this->human_discount;
674 }
675
676 $duration = $this->discount->coupon->duration ?? '';
677 $duration_in_months = $this->discount->coupon->duration_in_months ?? 0;
678
679 switch ( $duration ) {
680 case 'once':
681 return sprintf( '%s %s', $this->human_discount, __( 'once', 'surecart' ) );
682 case 'repeating':
683 // translators: %d is the number of months.
684 $months_label = sprintf( _n( '%d month', '%d months', $duration_in_months, 'surecart' ), $duration_in_months );
685 return sprintf( '%s for %s', $this->human_discount, $months_label );
686 default:
687 return $this->human_discount;
688 }
689 }
690
691 /**
692 * Get the shipping display amount attribute.
693 *
694 * @return string
695 */
696 public function getShippingDisplayAmountAttribute() {
697 return $this->shipping_amount ? Currency::format( $this->shipping_amount, $this->currency ) : __( 'Free', 'surecart' );
698 }
699
700 /**
701 * If the shipping address is required.
702 *
703 * @return bool
704 */
705 public function getShippingAddressRequiredAttribute(): bool {
706 return in_array( $this->shipping_address_accuracy_requirement, [ 'tax', 'full' ], true );
707 }
708
709 /**
710 * Get the paid display amount attribute.
711 *
712 * @return string
713 */
714 public function getPaidDisplayAmountAttribute() {
715 return Currency::format( $this->paid_amount, $this->currency );
716 }
717
718 /**
719 * Get the Paid at Date attribute.
720 *
721 * @return string
722 */
723 public function getPaidAtDateAttribute() {
724 return ! empty( $this->paid_at ) ? TimeDate::formatDate( $this->paid_at ) : '';
725 }
726
727 /**
728 * Get the proration display amount attribute.
729 *
730 * @return string
731 */
732 public function getProrationDisplayAmountAttribute() {
733 return Currency::format( $this->proration_amount, $this->currency );
734 }
735
736 /**
737 * Get the applied balance display amount attribute.
738 *
739 * @return string
740 */
741 public function getAppliedBalanceDisplayAmountAttribute() {
742 return Currency::format( $this->applied_balance_amount, $this->currency );
743 }
744
745 /**
746 * Get the credited balance display amount attribute.
747 *
748 * @return string
749 */
750 public function getCreditedBalanceDisplayAmountAttribute() {
751 return Currency::format( $this->credited_balance_amount, $this->currency );
752 }
753
754 /**
755 * Get the store currency amount due display amount attribute.
756 *
757 * @return string
758 */
759 public function getAmountDueDefaultCurrencyDisplayAmountAttribute() {
760 return Currency::format( $this->amount_due, $this->currency, [ 'convert' => false ] );
761 }
762
763 /**
764 * Get the store currency amount due display amount attribute.
765 *
766 * @return string
767 */
768 public function getCurrentCurrencyAttribute() {
769 return Currency::getCurrentCurrency();
770 }
771
772 /**
773 * Get the tax exclusive display amount attribute.
774 *
775 * @return string
776 */
777 public function getTaxExclusiveDisplayAmountAttribute() {
778 return Currency::format( $this->tax_exclusive_amount, $this->currency );
779 }
780
781 /**
782 * Get the tax inclusive display amount attribute.
783 *
784 * @return string
785 */
786 public function getTaxInclusiveDisplayAmountAttribute() {
787 return Currency::format( $this->tax_inclusive_amount, $this->currency );
788 }
789
790 /**
791 * Get the tax display amount attribute.
792 *
793 * @return string
794 */
795 public function getTaxDisplayAmountAttribute() {
796 return Currency::format( $this->tax_amount, $this->currency );
797 }
798 }
799