PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 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 All 59 releases
storeengine / includes / ajax / order.php

order.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/ajax/order.php

345 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Ajax;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use StoreEngine\Classes\AbstractAjaxHandler;
10 use StoreEngine\Classes\Exceptions\StoreEngineException;
11 use StoreEngine\Classes\OrderStatus\OrderStatus;
12 use StoreEngine\Utils\Constants;
13 use StoreEngine\Utils\Helper;
14 use StoreEngine\Addons\Stripe\StripeService;
15 use WP_Error;
16
17 class Order extends AbstractAjaxHandler {
18
19 public function __construct() {
20 $this->actions = [
21 // @TODO remove this action, it seems there is not use for it.
22 'get_order_payment_method_details' => [
23 'callback' => [ $this, 'get_order_payment_method_details' ],
24 'capability' => 'manage_options',
25 'fields' => [
26 'order_id' => 'id',
27 ],
28 ],
29 'change_order_status' => [
30 'callback' => [ $this, 'change_order_status' ],
31 'capability' => 'manage_options',
32 'fields' => [
33 'order_id' => 'id',
34 'status' => 'string',
35 ],
36 ],
37 'order/mark_as_paid' => [
38 'callback' => [ $this, 'mark_order_as_paid' ],
39 'capability' => 'manage_options',
40 'fields' => [
41 'order_id' => 'id',
42 ],
43 ],
44 'analytics/start_date' => [
45 'callback' => [ $this, 'analytics_start_date' ],
46 'capability' => 'manage_options',
47 ],
48 'order/get_notes' => [
49 'callback' => [ $this, 'get_order_notes' ],
50 'capability' => 'manage_options',
51 'fields' => [ 'id' => 'id' ],
52 ],
53 'order/add_note' => [
54 'callback' => [ $this, 'add_order_note' ],
55 'capability' => 'manage_options',
56 'fields' => [
57 'order_id' => 'id',
58 'note' => 'string',
59 'type' => 'text',
60 ],
61 ],
62 'order/delete_note' => [
63 'callback' => [ $this, 'delete_order_note' ],
64 'capability' => 'manage_options',
65 'fields' => [ 'id' => 'id' ],
66 ],
67 ];
68 }
69
70 protected function get_order_notes( array $payload ) {
71 $order = Helper::get_order( $payload['id'] ?? 0 );
72 if ( is_wp_error( $order ) ) {
73 wp_send_json_error( $order->get_error_message() );
74 }
75
76 wp_send_json_success( $order->get_order_notes() );
77 }
78
79 protected function add_order_note( array $payload ) {
80 if ( empty( $payload['order_id'] ) ) {
81 wp_send_json_error( __( 'Order ID is required', 'storeengine' ) );
82 }
83
84 if ( empty( $payload['note'] ) ) {
85 wp_send_json_error( __( 'Note content is required.', 'storeengine' ) );
86 }
87
88 if ( empty( $payload['type'] ) ) {
89 wp_send_json_error( __( 'Note type is required.', 'storeengine' ) );
90 }
91
92 if ( ! in_array( $payload['type'], [ 'admin', 'customer' ] ) ) {
93 wp_send_json_error( __( 'Invalid note type.', 'storeengine' ) );
94 }
95
96 $order = Helper::get_order( $payload['order_id'] );
97
98 if ( is_wp_error( $order ) ) {
99 wp_send_json_error( $order->get_error_message() );
100 }
101
102 if ( ! $order || ! $order->get_id() ) {
103 wp_send_json_error( __( 'Invalid order id.', 'storeengine' ) );
104 }
105
106 if ( $order->has_status( 'trash' ) ) {
107 wp_send_json_error( __( 'Please restore the order first.', 'storeengine' ) );
108 }
109
110 $note = $order->add_order_note( $payload['note'], 'customer' === $payload['type'], true );
111
112 if ( ! $note ) {
113 wp_send_json_error( __( 'Unable to add new note to the order.', 'storeengine' ) );
114 }
115
116 wp_send_json_success( \StoreEngine\Classes\Order::get_order_note( $note ) );
117 }
118
119 /**
120 * @throws StoreEngineException
121 */
122 protected function delete_order_note( array $payload ) {
123 if ( empty( $payload['id'] ) ) {
124 wp_send_json_error( __( 'Note ID is required.', 'storeengine' ) );
125 }
126
127 if ( \StoreEngine\Classes\Order::delete_order_note( $payload['id'] ) ) {
128 wp_send_json_success( __( 'Order note deleted.', 'storeengine' ) );
129 }
130
131 wp_send_json_error( __( 'Failed to delete note.', 'storeengine' ) );
132 }
133
134 protected function analytics_start_date() {
135 global $wpdb;
136
137 $date = wp_cache_get( 'storeengine_analytics_start_date' );
138
139 if ( false === $date ) {
140 $date = $wpdb->get_var( "SELECT date_created_gmt FROM {$wpdb->prefix}storeengine_orders ORDER BY date_created_gmt ASC LIMIT 1" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
141
142 if ( $date ) {
143 $date = gmdate( 'Y-m-d H:i', strtotime( $date ) );
144 wp_cache_set( 'storeengine_analytics_start_date', $date );
145 } else {
146 $date = gmdate( 'Y-m-d H:i' );
147 }
148 }
149
150 wp_send_json_success( $date );
151 }
152
153 public function get_order_payment_method_details( $payload ) {
154 if ( empty( $payload['order_id'] ) ) {
155 wp_send_json_error( esc_html__( 'Order ID is required.', 'storeengine' ) );
156 }
157
158 $order_model = new \StoreEngine\models\Order();
159 $order = $order_model->get_by_primary_key( $payload['order_id'] );
160 if ( ! $order ) {
161 wp_send_json_error( esc_html__( 'Order not found.', 'storeengine' ) );
162 }
163
164 if ( ! $order['payment_method'] ) {
165 wp_send_json_error( esc_html__( 'Payment method not found.', 'storeengine' ) );
166 }
167
168 if ( ! $order['payment_method'] ) {
169 wp_send_json_error( esc_html__( 'Payment method not found', 'storeengine' ) );
170 }
171
172 $gateway = Helper::get_payment_gateway( $order['payment_method'] );
173
174 if ( ! $gateway || 'stripe' !== $gateway->id ) {
175 wp_send_json_error( esc_html__( 'Stripe addon is not active', 'storeengine' ) );
176 }
177
178 if ( ! $gateway->is_enabled ) {
179 wp_send_json_error( esc_html__( 'Stripe is not enabled.', 'storeengine' ) );
180 }
181
182 $payment_method = StripeService::init()->get_payment_method( $order['meta']['stripe_payment_method_id'] );
183
184 if ( is_wp_error( $payment_method ) ) {
185 wp_send_json_error(
186 sprintf(
187 /* translators: %s: Error message. */
188 esc_html__( 'Error getting data from stripe. Error: %s', 'storeengine' ),
189 $payment_method->get_error_message()
190 )
191 );
192 }
193
194 wp_send_json_success( [
195 'brand' => $payment_method->card->brand,
196 'last4' => $payment_method->card->last4,
197 'expire_month' => $payment_method->card->exp_month,
198 'expire_year' => $payment_method->card->exp_year,
199 ] );
200 }
201
202 public function change_order_status( $payload ) {
203 if ( empty( $payload['order_id'] ) ) {
204 wp_send_json_error( esc_html__( 'Order ID is required.', 'storeengine' ) );
205 }
206
207 if ( empty( $payload['status'] ) ) {
208 wp_send_json_error( esc_html__( 'Order status is required.', 'storeengine' ) );
209 }
210
211 $order = Helper::get_order( $payload['order_id'] );
212
213 if ( is_wp_error( $order ) ) {
214 wp_send_json_error( $order->get_error_message() );
215 }
216
217 if ( ! $order ) {
218 wp_send_json_error( esc_html__( 'Order not found', 'storeengine' ) );
219 }
220
221 if ( 'trash' === $order->get_status() ) {
222 if ( 'restore' !== $payload['status'] ) {
223 return new WP_Error( 'storeengine_order_cannot_update_status', __( 'Please restore the order first.', 'storeengine' ), [ 'status' => 500 ] );
224 }
225
226 if ( ! $order->untrash() ) {
227 return new WP_Error( 'storeengine_order_cannot_untrash', __( 'Failed to restore the order.', 'storeengine' ), [ 'status' => 500 ] );
228 }
229
230 $order->set_status( OrderStatus::DRAFT );
231 $order->save();
232
233 } else {
234 if ( 'trash' === $payload['status'] ) {
235 $order_id = $order->get_id();
236 $force = ! empty( $payload['force'] );
237 if ( $force ) {
238 $order->delete( true );
239 $result = 0 === $order->get_id();
240 } else {
241 if ( 'trash' === $order->get_status() ) {
242 /* translators: %s: post type */
243 return new WP_Error( 'storeengine_order_already_trashed', __( 'The order has already been trashed.', 'storeengine' ), [ 'status' => 410 ] );
244 }
245
246 $order->trash();
247 $result = 'trash' === $order->get_status();
248 }
249
250 if ( ! $result ) {
251 return new WP_Error( 'storeengine_order_cannot_delete', __( 'Failed to delete order.', 'storeengine' ), [ 'status' => 500 ] );
252 }
253
254 do_action( 'storeengine/api/after_delete_order', $order, $order_id, $force );
255 } else {
256 $order->set_status( $payload['status'] );
257 $order->save();
258 }
259 }
260
261 wp_send_json_success( [
262 'id' => $order->get_id(),
263 'is_editable' => $order->is_editable(),
264 'currency' => $order->get_currency(),
265 'status' => $order->get_status(),
266 'paid_status' => $order->get_paid_status(),
267 'transaction_id' => $order->get_transaction_id(),
268 'meta' => $order->get_meta_data(),
269 'notes' => $order->get_order_notes(),
270 'type' => $order->get_type(),
271 'date_paid_gmt' => $order->get_date_paid_gmt() ? $order->get_date_paid_gmt()->format( 'Y-m-d H:i:s' ) : null,
272 'date_created_gmt' => $order->get_date_created_gmt() ? $order->get_date_created_gmt()->format( 'Y-m-d H:i:s' ) : null,
273 'order_placed_date_gmt' => $order->get_order_placed_date_gmt() ? $order->get_order_placed_date_gmt()->format( 'Y-m-d H:i:s' ) : null,
274 'order_placed_date' => $order->get_order_placed_date() ? $order->get_order_placed_date()->format( 'Y-m-d H:i:s' ) : null,
275 ] );
276 }
277
278 /**
279 * Forcibly mark an order as paid (admin escape hatch).
280 *
281 * Covers cases where a real payment happened at the gateway (e.g. Paddle) but
282 * the order was left unpaid/stranded in our system. Runs the full
283 * payment-complete flow via Order::mark_as_paid_force().
284 *
285 * @param array $payload
286 */
287 public function mark_order_as_paid( $payload ) {
288 if ( empty( $payload['order_id'] ) ) {
289 wp_send_json_error( esc_html__( 'Order ID is required.', 'storeengine' ) );
290 }
291
292 $order = Helper::get_order( $payload['order_id'] );
293
294 if ( is_wp_error( $order ) ) {
295 wp_send_json_error( $order->get_error_message() );
296 }
297
298 if ( ! $order ) {
299 wp_send_json_error( esc_html__( 'Order not found', 'storeengine' ) );
300 }
301
302 if ( 'trash' === $order->get_status() ) {
303 wp_send_json_error( esc_html__( 'Please restore the order first.', 'storeengine' ) );
304 }
305
306 if ( $order->is_paid() ) {
307 wp_send_json_error( esc_html__( 'Order is already paid.', 'storeengine' ) );
308 }
309
310 $current_user = wp_get_current_user();
311 $note = sprintf(
312 /* translators: %s: admin display name. */
313 __( 'Manually marked as paid by admin (%s).', 'storeengine' ),
314 $current_user && $current_user->exists() ? $current_user->display_name : __( 'unknown', 'storeengine' )
315 );
316
317 try {
318 $success = $order->mark_as_paid_force( $note );
319 } catch ( \Throwable $e ) {
320 Helper::log_error( $e );
321 wp_send_json_error( $e->getMessage() );
322 }
323
324 if ( empty( $success ) || ! $order->is_paid() ) {
325 wp_send_json_error( esc_html__( 'Failed to mark the order as paid.', 'storeengine' ) );
326 }
327
328 wp_send_json_success( [
329 'id' => $order->get_id(),
330 'is_editable' => $order->is_editable(),
331 'currency' => $order->get_currency(),
332 'status' => $order->get_status(),
333 'paid_status' => $order->get_paid_status(),
334 'transaction_id' => $order->get_transaction_id(),
335 'meta' => $order->get_meta_data(),
336 'notes' => $order->get_order_notes(),
337 'type' => $order->get_type(),
338 'date_paid_gmt' => $order->get_date_paid_gmt() ? $order->get_date_paid_gmt()->format( 'Y-m-d H:i:s' ) : null,
339 'date_created_gmt' => $order->get_date_created_gmt() ? $order->get_date_created_gmt()->format( 'Y-m-d H:i:s' ) : null,
340 'order_placed_date_gmt' => $order->get_order_placed_date_gmt() ? $order->get_order_placed_date_gmt()->format( 'Y-m-d H:i:s' ) : null,
341 'order_placed_date' => $order->get_order_placed_date() ? $order->get_order_placed_date()->format( 'Y-m-d H:i:s' ) : null,
342 ] );
343 }
344 }
345