blob: b3a8a6a83de86d2f92caefe402decc830f00e429 [file] [log] [blame]
swissChilif0cbdc32023-01-05 17:21:38 -05001<?php
2if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4}
5
6/**
7 * Trait for Pre-Orders compatibility.
8 */
9trait WC_Stripe_Pre_Orders_Trait {
10
11 /**
12 * Initialize pre-orders hook.
13 *
14 * @since 5.8.0
15 */
16 public function maybe_init_pre_orders() {
17 if ( ! $this->is_pre_orders_enabled() ) {
18 return;
19 }
20
21 $this->supports[] = 'pre-orders';
22
23 add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, [ $this, 'process_pre_order_release_payment' ] );
24 }
25
26 /**
27 * Checks if pre-orders are enabled on the site.
28 *
29 * @since 5.8.0
30 *
31 * @return bool
32 */
33 public function is_pre_orders_enabled() {
34 return class_exists( 'WC_Pre_Orders' );
35 }
36
37 /**
38 * Is $order_id a pre-order?
39 *
40 * @since 5.8.0
41 *
42 * @param int $order_id
43 * @return bool
44 */
45 public function has_pre_order( $order_id ) {
46 return $this->is_pre_orders_enabled() && class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id );
47 }
48
49 /**
50 * Returns boolean on whether current cart contains a pre-order item.
51 *
52 * @since 5.8.0
53 *
54 * @return bool
55 */
56 public function is_pre_order_item_in_cart() {
57 return $this->is_pre_orders_enabled() && class_exists( 'WC_Pre_Orders_Cart' ) && WC_Pre_Orders_Cart::cart_contains_pre_order();
58 }
59
60 /**
61 * Returns pre-order product from cart.
62 *
63 * @since 5.8.0
64 *
65 * @return object|null
66 */
67 public function get_pre_order_product_from_cart() {
68 if ( ! $this->is_pre_orders_enabled() || ! class_exists( 'WC_Pre_Orders_Cart' ) ) {
69 return false;
70 }
71 return WC_Pre_Orders_Cart::get_pre_order_product();
72 }
73
74 /**
75 * Returns pre-order product from order.
76 *
77 * @since 5.8.0
78 *
79 * @param int $order_id
80 *
81 * @return object|null
82 */
83 public function get_pre_order_product_from_order( $order_id ) {
84 if ( ! $this->is_pre_orders_enabled() || ! class_exists( 'WC_Pre_Orders_Order' ) ) {
85 return false;
86 }
87 return WC_Pre_Orders_Order::get_pre_order_product( $order_id );
88 }
89
90 /**
91 * Returns boolean on whether product is charged upon release.
92 *
93 * @since 5.8.0
94 *
95 * @param object $product
96 *
97 * @return bool
98 */
99 public function is_pre_order_product_charged_upon_release( $product ) {
100 return $this->is_pre_orders_enabled() && class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product );
101 }
102
103 /**
104 * Returns boolean on whether product is charged upfront.
105 *
106 * @since 5.8.0
107 *
108 * @param object $product
109 *
110 * @return bool
111 */
112 public function is_pre_order_product_charged_upfront( $product ) {
113 return $this->is_pre_orders_enabled() && class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upfront( $product );
114 }
115
116 /**
117 * Checks if we need to process pre-orders when
118 * a pre-order product is in the cart.
119 *
120 * @since 5.8.0
121 *
122 * @param int $order_id
123 *
124 * @return bool
125 */
126 public function maybe_process_pre_orders( $order_id ) {
127 return (
128 $this->has_pre_order( $order_id ) &&
129 WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) &&
130 ! is_wc_endpoint_url( 'order-pay' )
131 );
132 }
133
134 /**
135 * Remove order meta.
136 *
137 * @param object $order
138 */
139 public function remove_order_source_before_retry( $order ) {
140 $order->delete_meta_data( '_stripe_source_id' );
141 $order->delete_meta_data( '_stripe_card_id' );
142 $order->save();
143 }
144
145 /**
146 * Marks the order as pre-ordered.
147 * The native function is wrapped so we can call it separately and more easily mock it in our tests.
148 *
149 * @param object $order
150 */
151 public function mark_order_as_pre_ordered( $order ) {
152 if ( ! class_exists( 'WC_Pre_Orders_Order' ) ) {
153 return;
154 }
155 WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
156 }
157
158 /**
159 * Process the pre-order when pay upon release is used.
160 *
161 * @param int $order_id
162 *
163 * @return array
164 */
165 public function process_pre_order( $order_id ) {
166 try {
167 $order = wc_get_order( $order_id );
168
169 // This will throw exception if not valid.
170 $this->validate_minimum_order_amount( $order );
171
172 $prepared_source = $this->prepare_source( get_current_user_id(), true );
173
174 // We need a source on file to continue.
175 if ( empty( $prepared_source->customer ) || empty( $prepared_source->source ) ) {
176 throw new WC_Stripe_Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
177 }
178
179 // Setup the response early to allow later modifications.
180 $response = [
181 'result' => 'success',
182 'redirect' => $this->get_return_url( $order ),
183 ];
184
185 $this->save_source_to_order( $order, $prepared_source );
186
187 // Try setting up a payment intent.
188 $intent_secret = $this->setup_intent( $order, $prepared_source );
189 if ( ! empty( $intent_secret ) ) {
190 $response['setup_intent_secret'] = $intent_secret;
191 return $response;
192 }
193
194 // Remove cart.
195 WC()->cart->empty_cart();
196
197 // Is pre ordered!
198 $this->mark_order_as_pre_ordered( $order );
199
200 // Return thank you page redirect
201 return $response;
202 } catch ( WC_Stripe_Exception $e ) {
203 wc_add_notice( $e->getLocalizedMessage(), 'error' );
204 WC_Stripe_Logger::log( 'Pre Orders Error: ' . $e->getMessage() );
205
206 return [
207 'result' => 'success',
208 'redirect' => $order->get_checkout_payment_url( true ),
209 ];
210 }
211 }
212
213 /**
214 * Process a pre-order payment when the pre-order is released.
215 *
216 * @param WC_Order $order
217 * @param bool $retry
218 *
219 * @return void
220 */
221 public function process_pre_order_release_payment( $order, $retry = true ) {
222 try {
223 $source = $this->prepare_order_source( $order );
224 $response = $this->create_and_confirm_intent_for_off_session( $order, $source );
225
226 $is_authentication_required = $this->is_authentication_required_for_payment( $response );
227
228 if ( ! empty( $response->error ) && ! $is_authentication_required ) {
229 if ( ! $retry ) {
230 throw new Exception( $response->error->message );
231 }
232 $this->remove_order_source_before_retry( $order );
233 $this->process_pre_order_release_payment( $order, false );
234 } elseif ( $is_authentication_required ) {
235 $charge = end( $response->error->payment_intent->charges->data );
236 $id = $charge->id;
237
238 $order->set_transaction_id( $id );
239 /* translators: %s is the charge Id */
240 $order->update_status( 'failed', sprintf( __( 'Stripe charge awaiting authentication by user: %s.', 'woocommerce-gateway-stripe' ), $id ) );
241 if ( is_callable( [ $order, 'save' ] ) ) {
242 $order->save();
243 }
244
245 WC_Emails::instance();
246
247 do_action( 'wc_gateway_stripe_process_payment_authentication_required', $order );
248
249 throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
250 } else {
251 // Successful
252 $this->process_response( end( $response->charges->data ), $order );
253 }
254 } catch ( Exception $e ) {
255 $error_message = is_callable( [ $e, 'getLocalizedMessage' ] ) ? $e->getLocalizedMessage() : $e->getMessage();
256 /* translators: error message */
257 $order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $error_message );
258
259 // Mark order as failed if not already set,
260 // otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
261 if ( ! $order->has_status( 'failed' ) ) {
262 $order->update_status( 'failed', $order_note );
263 } else {
264 $order->add_order_note( $order_note );
265 }
266 }
267 }
268}