PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.6.1
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.6.1
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 / Checkout.php
surecart / app / src / Models Last commit date
Blocks 5 days ago Concerns 6 months ago Traits 3 weeks ago Wordpress 1 year ago AbandonedCheckout.php 1 year ago AbandonedCheckoutProtocol.php 3 years ago Account.php 10 months ago AccountPortalSession.php 4 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 1 week ago Brand.php 4 months ago BulkAction.php 2 years ago Bump.php 1 year ago BundleItem.php 5 days ago BuyLink.php 2 years ago CancellationAct.php 1 year ago CancellationReason.php 3 years ago Charge.php 9 months ago Checkout.php 5 days ago Click.php 1 year ago Collection.php 3 months ago CommissionStructure.php 2 years ago Component.php 5 months ago Coupon.php 3 months ago Customer.php 3 weeks ago CustomerLink.php 4 years ago CustomerNotificationProtocol.php 4 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 4 years ago Event.php 4 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 1 week ago Invoice.php 1 year ago License.php 1 year ago LineItem.php 5 days ago ManualPaymentMethod.php 1 year ago Media.php 1 month ago Model.php 1 week ago ModelInterface.php 4 years ago Order.php 3 weeks ago OrderProtocol.php 3 years ago ParcelTemplate.php 4 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 4 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 5 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 5 months ago ReviewProtocol.php 5 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 4 years ago Subscription.php 3 weeks ago SubscriptionProtocol.php 4 years ago Swap.php 1 year ago TaxOverride.php 2 years ago TaxProtocol.php 4 years ago TaxRegistration.php 1 year ago TaxZone.php 4 years ago Upload.php 4 years ago Upsell.php 1 year ago UpsellFunnel.php 1 year ago User.php 1 month ago Variant.php 1 week 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
830 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 * Count of line items excluding bundle containers.
300 *
301 * A bundle serializes as a container line item plus one line item per
302 * component; only the components are delivered, so counting raw line
303 * items reads N+1. Containers are found via `is_bundle_parent` when
304 * `price.product` is expanded, or via the parent ids components carry
305 * in `bundle_line_item` when it isn't.
306 *
307 * @return int
308 */
309 public function getDeliveredItemsCountAttribute() {
310 $items = $this->line_items->data ?? [];
311 $total = $this->line_items->pagination->count ?? count( $items );
312
313 $container_ids = [];
314 foreach ( $items as $item ) {
315 if ( ! empty( $item->component_line_item ) && ! empty( $item->bundle_line_item ) ) {
316 $parent_id = is_object( $item->bundle_line_item ) ? $item->bundle_line_item->id : $item->bundle_line_item;
317 if ( $parent_id ) {
318 $container_ids[ $parent_id ] = true;
319 }
320 }
321 if ( ! empty( $item->is_bundle_parent ) ) {
322 $container_ids[ $item->id ] = true;
323 }
324 }
325
326 return max( $total - count( $container_ids ), 0 );
327 }
328
329 /**
330 * Get the has recurring attribute.
331 *
332 * Do any line items have a recurring price?
333 *
334 * @return bool
335 */
336 public function getHasRecurringAttribute() {
337 return array_reduce(
338 $this->line_items->data ?? [],
339 function ( $carry, $item ) {
340 return $carry || isset( $item->price->recurring_interval );
341 },
342 false
343 );
344 }
345
346 /**
347 * Set attributes during write actions.
348 *
349 * @return void
350 */
351 protected function setWriteAttributes() {
352 $this->setAttribute( 'ip_address', $this->getIPAddress() );
353 if ( isset( $_COOKIE['sc_click_id'] ) ) {
354 $this->setAttribute( 'last_click', $_COOKIE['sc_click_id'] );
355 }
356 }
357
358 /**
359 * Set the upsell funnel attribute
360 *
361 * @param object $value The data array.
362 * @return void
363 */
364 public function setUpsellFunnelAttribute( $value ) {
365 $this->setRelation( 'upsell_funnel', $value, UpsellFunnel::class );
366 }
367
368 /**
369 * Set the upsell funnel attribute
370 *
371 * @param object $value The data array.
372 * @return void
373 */
374 public function setCurrentUpsellAttribute( $value ) {
375 $this->setRelation( 'current_upsell', $value, Upsell::class );
376 }
377
378 /**
379 * Set the recommended bumps attribute
380 *
381 * @param object $value Subscription data array.
382 * @return void
383 */
384 public function setRecommendedBumpsAttribute( $value ) {
385 $this->setCollection( 'recommended_bumps', $value, Bump::class );
386 }
387
388 /**
389 * Set the checkout fees attribute
390 *
391 * @param object $value Subscription data array.
392 * @return void
393 */
394 public function setCheckoutFeesAttribute( $value ) {
395 $this->setCollection( 'checkout_fees', $value, Fee::class );
396 }
397
398 /**
399 * Set the shipping fees attribute
400 *
401 * @param object $value Subscription data array.
402 * @return void
403 */
404 public function setShippingFeesAttribute( $value ) {
405 $this->setCollection( 'shipping_fees', $value, Fee::class );
406 }
407
408 /**
409 * Get the display checkout fees amount attribute.
410 *
411 * @return string
412 */
413 public function getCheckoutFeesDisplayAmountAttribute() {
414 return ! empty( $this->checkout_fees_amount ) ? Currency::format( $this->checkout_fees_amount, $this->currency ) : '';
415 }
416
417 /**
418 * Get the display shipping fees amount attribute.
419 *
420 * @return string
421 */
422 public function getShippingFeesDisplayAmountAttribute() {
423 return ! empty( $this->shipping_fees_amount ) ? Currency::format( $this->shipping_fees_amount, $this->currency ) : '';
424 }
425
426 /**
427 * Create a new model
428 *
429 * @param array $attributes Attributes to create.
430 *
431 * @return $this|\WP_Error|false
432 */
433 protected function create( $attributes = [] ) {
434 $this->setWriteAttributes();
435 return parent::create( $attributes );
436 }
437
438 /**
439 * Update the model
440 *
441 * @param array $attributes Attributes to create.
442 *
443 * @return $this|\WP_Error|false
444 */
445 protected function update( $attributes = [] ) {
446 $this->setWriteAttributes();
447
448 return parent::update( $attributes );
449 }
450
451 /**
452 * Get the IP address of the user
453 *
454 * TOD0: Move this to a helper class.
455 *
456 * @return string
457 */
458 protected function getIPAddress() {
459 return $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
460 }
461
462 /**
463 * Set the product attribute
464 *
465 * @param object $value Product properties.
466 * @return void
467 */
468 public function setLineItemsAttribute( $value ) {
469 $this->setCollection( 'line_items', $value, LineItem::class );
470 }
471
472 /**
473 * Finalize the session for checkout.
474 *
475 * @return $this|\WP_Error
476 */
477 protected function manuallyPay() {
478 if ( $this->fireModelEvent( 'manually_paying' ) === false ) {
479 return false;
480 }
481
482 if ( empty( $this->attributes['id'] ) ) {
483 return new \WP_Error( 'not_saved', 'Please create the checkout session.' );
484 }
485
486 $finalized = \SureCart::request(
487 $this->endpoint . '/' . $this->attributes['id'] . '/manually_pay/',
488 [
489 'method' => 'PATCH',
490 'query' => $this->query,
491 'body' => [
492 $this->object_name => $this->getAttributes(),
493 ],
494 ]
495 );
496
497 if ( is_wp_error( $finalized ) ) {
498 return $finalized;
499 }
500
501 $this->resetAttributes();
502
503 $this->fill( $finalized );
504
505 $this->fireModelEvent( 'manually_paid' );
506
507 return $this;
508 }
509
510 /**
511 * Cancel an checkout
512 *
513 * @return $this|\WP_Error
514 */
515 protected function cancel() {
516 if ( $this->fireModelEvent( 'cancelling' ) === false ) {
517 return false;
518 }
519
520 if ( empty( $this->attributes['id'] ) ) {
521 return new \WP_Error( 'not_saved', 'Please create the order.' );
522 }
523
524 $cancelled = $this->makeRequest(
525 [
526 'method' => 'PATCH',
527 'query' => $this->query,
528 'body' => [
529 $this->object_name => $this->getAttributes(),
530 ],
531 ],
532 $this->endpoint . '/' . $this->attributes['id'] . '/cancel/'
533 );
534
535 if ( is_wp_error( $cancelled ) ) {
536 return $cancelled;
537 }
538
539 $this->resetAttributes();
540
541 $this->fill( $cancelled );
542
543 $this->fireModelEvent( 'cancelled' );
544
545 return $this;
546 }
547
548 /**
549 * Offer a bump.
550 *
551 * @param string|\SureCart\Models\Bump $bump The bump object.
552 *
553 * @return true|\WP_Error
554 */
555 protected function offerBump( $bump ) {
556 if ( $this->fireModelEvent( 'offering_bump' ) === false ) {
557 return false;
558 }
559
560 if ( empty( $this->attributes['id'] ) ) {
561 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
562 }
563
564 $id = is_a( $bump, Bump::class ) ? $bump->id : $bump;
565
566 if ( empty( $id ) ) {
567 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
568 }
569
570 $offered = $this->makeRequest(
571 [
572 'method' => 'PATCH',
573 'query' => $this->query,
574 'body' => [
575 $this->object_name => $this->getAttributes(),
576 ],
577 ],
578 $this->endpoint . '/' . $this->attributes['id'] . '/offer_bump/' . $id
579 );
580
581 if ( is_wp_error( $offered ) ) {
582 return $offered;
583 }
584
585 return true;
586 }
587
588 /**
589 * Offer an upsell.
590 *
591 * @param string|\SureCart\Models\Upsell $upsell The upsell object.
592 *
593 * @return true|\WP_Error
594 */
595 protected function offerUpsell( $upsell ) {
596 if ( $this->fireModelEvent( 'offering_upsell' ) === false ) {
597 return false;
598 }
599
600 if ( empty( $this->attributes['id'] ) ) {
601 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
602 }
603
604 $id = is_a( $upsell, Upsell::class ) ? $upsell->id : $upsell;
605
606 if ( empty( $id ) ) {
607 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
608 }
609
610 $offered = $this->makeRequest(
611 [
612 'method' => 'PATCH',
613 'query' => $this->query,
614 'body' => [
615 $this->object_name => $this->getAttributes(),
616 ],
617 ],
618 $this->endpoint . '/' . $this->attributes['id'] . '/offer_upsell/' . $id
619 );
620
621 if ( is_wp_error( $offered ) ) {
622 return $offered;
623 }
624
625 return true;
626 }
627
628 /**
629 * Decline an upsell.
630 *
631 * @param string|\SureCart\Models\Upsell $upsell The upsell object.
632 *
633 * @return $this|\WP_Error
634 */
635 protected function declineUpsell( $upsell ) {
636 if ( $this->fireModelEvent( 'declining_upsell' ) === false ) {
637 return false;
638 }
639
640 if ( empty( $this->attributes['id'] ) ) {
641 return new \WP_Error( 'not_saved', 'Please create the checkout.' );
642 }
643
644 $id = is_a( $upsell, Upsell::class ) ? $upsell->id : $upsell;
645
646 if ( empty( $id ) ) {
647 return new \WP_Error( 'not_saved', 'Please pass an upsell.' );
648 }
649
650 $declined = $this->makeRequest(
651 [
652 'method' => 'PATCH',
653 'query' => $this->query,
654 'body' => [
655 $this->object_name => $this->getAttributes(),
656 ],
657 ],
658 $this->endpoint . '/' . $this->attributes['id'] . '/decline_upsell/' . $id
659 );
660
661 if ( is_wp_error( $declined ) ) {
662 return $declined;
663 }
664
665 $this->resetAttributes();
666
667 $this->fill( $declined );
668
669 $this->fireModelEvent( 'declined' );
670
671 return $this;
672 }
673
674 /**
675 * Get the human discount attribute.
676 *
677 * @return string
678 */
679 public function getHumanDiscountAttribute() {
680 if ( empty( $this->discount->coupon ) ) {
681 return '';
682 }
683
684 if ( ! empty( $this->discount->coupon->percent_off ) ) {
685 if ( ! empty( $this->discount->coupon->amount_off ) && ! empty( $this->currency ) ) {
686 return Currency::format( $this->discount->coupon->amount_off, $this->currency );
687 }
688
689 $percent = sprintf( '%d%%', (int) $this->discount->coupon->percent_off );
690 // translators: %s is the discount percentage (e.g. "10%").
691 return sprintf( __( '%s off', 'surecart' ), $percent );
692 }
693
694 return '';
695 }
696
697 /**
698 * Get the human discount with duration attribute.
699 *
700 * @return string
701 */
702 public function getHumanDiscountWithDurationAttribute() {
703 if ( ! $this->hasRecurring ) {
704 return $this->human_discount;
705 }
706
707 $duration = $this->discount->coupon->duration ?? '';
708 $duration_in_months = $this->discount->coupon->duration_in_months ?? 0;
709
710 switch ( $duration ) {
711 case 'once':
712 return sprintf( '%s %s', $this->human_discount, __( 'once', 'surecart' ) );
713 case 'repeating':
714 // translators: %d is the number of months.
715 $months_label = sprintf( _n( '%d month', '%d months', $duration_in_months, 'surecart' ), $duration_in_months );
716 return sprintf( '%s for %s', $this->human_discount, $months_label );
717 default:
718 return $this->human_discount;
719 }
720 }
721
722 /**
723 * Get the shipping display amount attribute.
724 *
725 * @return string
726 */
727 public function getShippingDisplayAmountAttribute() {
728 return $this->shipping_amount ? Currency::format( $this->shipping_amount, $this->currency ) : __( 'Free', 'surecart' );
729 }
730
731 /**
732 * If the shipping address is required.
733 *
734 * @return bool
735 */
736 public function getShippingAddressRequiredAttribute(): bool {
737 return in_array( $this->shipping_address_accuracy_requirement, [ 'tax', 'full' ], true );
738 }
739
740 /**
741 * Get the paid display amount attribute.
742 *
743 * @return string
744 */
745 public function getPaidDisplayAmountAttribute() {
746 return Currency::format( $this->paid_amount, $this->currency );
747 }
748
749 /**
750 * Get the Paid at Date attribute.
751 *
752 * @return string
753 */
754 public function getPaidAtDateAttribute() {
755 return ! empty( $this->paid_at ) ? TimeDate::formatDate( $this->paid_at ) : '';
756 }
757
758 /**
759 * Get the proration display amount attribute.
760 *
761 * @return string
762 */
763 public function getProrationDisplayAmountAttribute() {
764 return Currency::format( $this->proration_amount, $this->currency );
765 }
766
767 /**
768 * Get the applied balance display amount attribute.
769 *
770 * @return string
771 */
772 public function getAppliedBalanceDisplayAmountAttribute() {
773 return Currency::format( $this->applied_balance_amount, $this->currency );
774 }
775
776 /**
777 * Get the credited balance display amount attribute.
778 *
779 * @return string
780 */
781 public function getCreditedBalanceDisplayAmountAttribute() {
782 return Currency::format( $this->credited_balance_amount, $this->currency );
783 }
784
785 /**
786 * Get the store currency amount due display amount attribute.
787 *
788 * @return string
789 */
790 public function getAmountDueDefaultCurrencyDisplayAmountAttribute() {
791 return Currency::format( $this->amount_due, $this->currency, [ 'convert' => false ] );
792 }
793
794 /**
795 * Get the store currency amount due display amount attribute.
796 *
797 * @return string
798 */
799 public function getCurrentCurrencyAttribute() {
800 return Currency::getCurrentCurrency();
801 }
802
803 /**
804 * Get the tax exclusive display amount attribute.
805 *
806 * @return string
807 */
808 public function getTaxExclusiveDisplayAmountAttribute() {
809 return Currency::format( $this->tax_exclusive_amount, $this->currency );
810 }
811
812 /**
813 * Get the tax inclusive display amount attribute.
814 *
815 * @return string
816 */
817 public function getTaxInclusiveDisplayAmountAttribute() {
818 return Currency::format( $this->tax_inclusive_amount, $this->currency );
819 }
820
821 /**
822 * Get the tax display amount attribute.
823 *
824 * @return string
825 */
826 public function getTaxDisplayAmountAttribute() {
827 return Currency::format( $this->tax_amount, $this->currency );
828 }
829 }
830