PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.9.1
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.9.1
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / inc / gateways / subscriptions / class-lp-subscription-manager.php

class-lp-subscription-manager.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.9.1, at inc/gateways/subscriptions/class-lp-subscription-manager.php

714 lines 27.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Subscription orchestration service for gateway webhooks.
4 *
5 * @since 4.3.4
6 */
7
8 defined( 'ABSPATH' ) || exit();
9
10 if ( ! class_exists( 'LP_Subscription_Manager' ) ) {
11 class LP_Subscription_Manager {
12 /**
13 * @var LP_Subscription_Manager|null
14 */
15 protected static $_instance = null;
16
17 const STATUS_TRIAL = 'trial';
18 const STATUS_ACTIVATED = 'activated';
19 const STATUS_RENEWED = 'renewed';
20 const STATUS_CANCELLED = 'cancelled';
21 const STATUS_EXPIRED = 'expired';
22 const STATUS_SUSPENDED = 'suspended';
23
24 /**
25 * Get singleton instance of subscription manager.
26 *
27 * @return LP_Subscription_Manager
28 */
29 public static function instance(): LP_Subscription_Manager {
30 if ( is_null( self::$_instance ) ) {
31 self::$_instance = new static();
32 }
33
34 return self::$_instance;
35 }
36
37 /**
38 * Process a normalized subscription webhook event.
39 *
40 * Flow:
41 * - Build deterministic event id.
42 * - Resolve parent order.
43 * - Check order state for duplicates (order-state-based idempotency).
44 * - Route by event type (activate, renew, fail, cancel, expire, update).
45 * - Sync subscription meta and update order state.
46 *
47 * @param LP_Gateway_Abstract $gateway
48 * @param array $event Normalized payload from gateway::normalize_subscription_event().
49 *
50 * @return array{
51 * status:string,
52 * event_id:string,
53 * event_type:string,
54 * status_code:int,
55 * order_id?:int,
56 * renewal_order_id?:int,
57 * message?:string
58 * }
59 * @deprecated 4.3.8 Use LP_Gateway_Abstract::capture_subscription_webhook() instead.
60 */
61 // public function process_webhook_event( LP_Gateway_Abstract $gateway, array $event ): array {
62 // $gateway_id = $gateway->get_id();
63 // $event_id = sanitize_text_field( (string) ( $event['event_id'] ?? '' ) );
64 // $event_type = sanitize_key( (string) ( $event['event_type'] ?? '' ) );
65 //
66 // if ( empty( $event_id ) ) {
67 // $event_id = md5( wp_json_encode( $event ) );
68 // }
69 //
70 // $response = array(
71 // 'status' => 'ignored',
72 // 'event_id' => $event_id,
73 // 'event_type' => $event_type,
74 // 'status_code' => 200,
75 // );
76 //
77 // try {
78 // $order_subscription_id = $this->resolve_parent_order_id( $event );
79 // $order_subscription = $order_subscription_id ? learn_press_get_order( $order_subscription_id ) : false;
80 // $this->validate_parent_subscription_binding( $order_subscription, $event );
81 //
82 // if ( $this->is_event_already_handled( $event_type, $event_id, $order_subscription, $event ) ) {
83 // return array(
84 // 'status' => 'duplicate',
85 // 'event_id' => $event_id,
86 // 'event_type' => $event_type,
87 // 'status_code' => 200,
88 // );
89 // }
90 //
91 // if ( $order_subscription ) {
92 // $this->sync_subscription_meta( $order_subscription->get_id(), $event );
93 // }
94 //
95 // switch ( $event_type ) {
96 // case 'subscription_activated':
97 // if ( ! $order_subscription ) {
98 // throw new Exception( __( 'Order not found.', 'learnpress' ) );
99 // }
100 //
101 // $this->update_subscription_status( $order_subscription->get_id(), 'active' );
102 // // Team convention: activated means the subscription order becomes completed.
103 // $this->mark_parent_payment_completed( $order_subscription, $event );
104 // $order_subscription->add_note( __( 'Subscription activated.', 'learnpress' ) );
105 // do_action( 'learn-press/subscription/activated', $order_subscription->get_id(), $event, $gateway_id );
106 //
107 // $response['status'] = 'success';
108 // $response['order_id'] = $order_subscription->get_id();
109 // break;
110 // case 'initial_payment_succeeded':
111 // if ( ! $order_subscription ) {
112 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
113 // }
114 //
115 // $this->update_subscription_status( $order_subscription->get_id(), 'active' );
116 // $this->mark_parent_payment_completed( $order_subscription, $event );
117 // $order_subscription->add_note( __( 'Initial subscription payment succeeded.', 'learnpress' ) );
118 // do_action( 'learn-press/subscription/initial-payment-succeeded', $order_subscription->get_id(), $event, $gateway_id );
119 //
120 // $response['status'] = 'success';
121 // $response['order_id'] = $order_subscription->get_id();
122 // break;
123 // case 'renewal_payment_succeeded':
124 // if ( ! $order_subscription ) {
125 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
126 // }
127 //
128 // $this->update_subscription_status( $order_subscription->get_id(), 'active' );
129 // /*
130 // * PayPal may report the first successful charge as PAYMENT.SALE.COMPLETED.
131 // * If the parent subscription order is still not completed, treat this as
132 // * the initial payment instead of creating a renewal order.
133 // */
134 // if ( 'paypal' === $gateway_id && ! $order_subscription->is_completed() ) {
135 // $this->mark_parent_payment_completed( $order_subscription, $event );
136 // $order_subscription->add_note( __( 'Initial subscription payment succeeded.', 'learnpress' ) );
137 // do_action( 'learn-press/subscription/initial-payment-succeeded', $order_subscription->get_id(), $event, $gateway_id );
138 //
139 // $response['status'] = 'success';
140 // $response['order_id'] = $order_subscription->get_id();
141 // break;
142 // }
143 //
144 // $order_renew = $this->create_renewal_order( $order_subscription, $event, LP_ORDER_COMPLETED );
145 // $order_subscription->add_note( __( 'Subscription renewal payment succeeded.', 'learnpress' ) );
146 // do_action( 'learn-press/subscription/renewal-order-created', $order_renew->get_id(), $order_subscription->get_id(), $event, $gateway_id );
147 // do_action( 'learn-press/subscription/renewed', $order_subscription->get_id(), $order_renew->get_id(), $event, $gateway_id );
148 //
149 // $response['status'] = 'success';
150 // $response['order_id'] = $order_subscription->get_id();
151 // $response['renewal_order_id'] = $order_renew->get_id();
152 // break;
153 // case 'renewal_payment_failed':
154 // if ( ! $order_subscription ) {
155 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
156 // }
157 //
158 // $this->update_subscription_status( $order_subscription->get_id(), 'past_due' );
159 // $order_renew = $this->create_renewal_order( $order_subscription, $event, LP_ORDER_FAILED );
160 // $order_subscription->add_note( __( 'Subscription renewal payment failed.', 'learnpress' ) );
161 // do_action( 'learn-press/subscription/renewal-order-created', $order_renew->get_id(), $order_subscription->get_id(), $event, $gateway_id );
162 // do_action( 'learn-press/subscription/payment-failed', $order_subscription->get_id(), $order_renew->get_id(), $event, $gateway_id );
163 //
164 // $response['status'] = 'failed';
165 // $response['order_id'] = $order_subscription->get_id();
166 // $response['renewal_order_id'] = $order_renew->get_id();
167 // break;
168 // case 'subscription_cancelled':
169 // if ( ! $order_subscription ) {
170 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
171 // }
172 //
173 // $this->update_subscription_status( $order_subscription->get_id(), 'cancelled' );
174 // $order_subscription->add_note( __( 'Subscription cancelled.', 'learnpress' ) );
175 // do_action( 'learn-press/subscription/cancelled', $order_subscription->get_id(), $event, $gateway_id );
176 //
177 // $response['status'] = 'cancelled';
178 // $response['order_id'] = $order_subscription->get_id();
179 // break;
180 // case 'subscription_suspended':
181 // if ( ! $order_subscription ) {
182 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
183 // }
184 //
185 // $this->update_subscription_status( $order_subscription->get_id(), 'suspended' );
186 // $order_subscription->add_note( __( 'Subscription suspended.', 'learnpress' ) );
187 // do_action( 'learn-press/subscription/suspended', $order_subscription->get_id(), $event, $gateway_id );
188 //
189 // $response['status'] = 'suspended';
190 // $response['order_id'] = $order_subscription->get_id();
191 // break;
192 // case 'subscription_expired':
193 // if ( ! $order_subscription ) {
194 // throw new Exception( __( 'Parent subscription order not found.', 'learnpress' ) );
195 // }
196 //
197 // $this->update_subscription_status( $order_subscription->get_id(), 'expired' );
198 // $order_subscription->add_note( __( 'Subscription expired.', 'learnpress' ) );
199 // do_action( 'learn-press/subscription/expired', $order_subscription->get_id(), $event, $gateway_id );
200 //
201 // $response['status'] = 'expired';
202 // $response['order_id'] = $order_subscription->get_id();
203 // break;
204 // case 'subscription_updated':
205 // if ( $order_subscription ) {
206 // $order_subscription->add_note( __( 'Subscription updated.', 'learnpress' ) );
207 // $response['order_id'] = $order_subscription->get_id();
208 // }
209 // $response['status'] = 'updated';
210 // break;
211 // default:
212 // $response['status'] = 'ignored';
213 // break;
214 // }
215 // } catch ( Throwable $e ) {
216 // $response['status'] = 'error';
217 // $response['status_code'] = 400;
218 // $response['message'] = $e->getMessage();
219 // error_log( 'LP_Subscription_Manager: ' . $e->getMessage() );
220 // }
221 //
222 // return $response;
223 // }
224
225 /**
226 * Check whether the webhook event outcome is already reflected in order state.
227 *
228 * Replaces the old LP_Subscription_Event_Store transient-based approach with
229 * order-state checks: status, transaction_id, and event_id meta.
230 *
231 * @since 4.3.5
232 *
233 * @param string $event_type Normalized event type.
234 * @param string $event_id Provider event ID.
235 * @param LP_Order|false $order_subscription Resolved subscription order (false when not found).
236 * @param array $event Full normalized event payload.
237 *
238 * @return bool True if the event outcome is already reflected in the database.
239 * @deprecated 4.3.8
240 */
241 /*private function is_event_already_handled( string $event_type, string $event_id, $order_subscription, array $event ): bool {
242
243 if ( ! $order_subscription instanceof LP_Order ) {
244 return false;
245 }
246
247 $order_subscription_id = $order_subscription->get_id();
248
249 switch ( $event_type ) {
250 case 'subscription_activated':
251 $sub_status = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
252 return in_array( $sub_status, array( 'active', 'trialing' ), true );
253
254 case 'initial_payment_succeeded':
255 $sub_status = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
256 if ( ! in_array( $sub_status, array( 'active', 'trialing' ), true ) || ! $order_subscription->is_completed() ) {
257 return false;
258 }
259
260 $event_transaction_id = sanitize_text_field( (string) ( $event['transaction_id'] ?? '' ) );
261 if ( '' === $event_transaction_id ) {
262 return true;
263 }
264
265 $saved_transaction_id = sanitize_text_field(
266 (string) get_post_meta( $order_subscription_id, '_transaction_id', true )
267 );
268 if ( '' === $saved_transaction_id ) {
269 // Need one more pass to backfill transaction id into parent order.
270 return false;
271 }
272
273 return $saved_transaction_id === $event_transaction_id;
274
275 case 'renewal_payment_succeeded':
276 case 'renewal_payment_failed':
277 $event_transaction_id = sanitize_text_field( (string) ( $event['transaction_id'] ?? '' ) );
278 if ( ! empty( $event_transaction_id ) ) {
279 $parent_transaction_id = sanitize_text_field(
280 (string) get_post_meta( $order_subscription_id, '_transaction_id', true )
281 );
282 if ( '' !== $parent_transaction_id && $parent_transaction_id === $event_transaction_id ) {
283 return true;
284 }
285 }
286
287 $renewal_key = $this->get_renewal_key( $event );
288 if ( ! empty( $renewal_key ) && $this->find_renewal_order_by_key( $order_subscription_id, $renewal_key ) ) {
289 return true;
290 }
291 if ( ! empty( $event_id ) && $this->find_renewal_order_by_event( $order_subscription_id, $event_id ) ) {
292 return true;
293 }
294 return false;
295
296 case 'subscription_cancelled':
297 $current = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
298 return 'cancelled' === $current;
299
300 case 'subscription_suspended':
301 $current = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
302 return 'suspended' === $current;
303
304 case 'subscription_expired':
305 $current = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
306 return 'expired' === $current;
307
308 case 'subscription_updated':
309 $last_event = get_post_meta( $order_subscription_id, LP_Gateway_Abstract::META_SUBSCRIPTION_LAST_EVENT_ID, true );
310 return ! empty( $event_id ) && $last_event === $event_id;
311 default:
312 return false;
313 }
314 }*/
315
316 /**
317 * Resolve parent order id for an incoming event.
318 *
319 * Resolution priority:
320 * 1) event[parent_order_id]
321 * 2) event[metadata][lp_order_id]
322 * 3) lookup by subscription_id in order meta.
323 *
324 * @param array $event Normalized event payload.
325 *
326 * @return int Parent order id or 0 when not found.
327 * @deprecated 4.3.8
328 */
329 /*public function resolve_parent_order_id( array $event ): int {
330
331 $order_subscription_id = absint( $event['parent_order_id'] ?? 0 );
332 if ( $order_subscription_id ) {
333 return $order_subscription_id;
334 }
335 $metadata = (array) ( $event['metadata'] ?? array() );
336 if ( ! empty( $metadata['lp_order_id'] ) ) {
337 return absint( $metadata['lp_order_id'] );
338 }
339
340 $subscription_id = sanitize_text_field( (string) ( $event['subscription_id'] ?? '' ) );
341 if ( empty( $subscription_id ) ) {
342 return 0;
343 }
344
345 $order_ids = get_posts(
346 array(
347 'post_type' => LP_ORDER_CPT,
348 'post_status' => 'any',
349 'posts_per_page' => 1,
350 'fields' => 'ids',
351 'meta_query' => array(
352 array(
353 'key' => LP_Gateway_Abstract::META_SUBSCRIPTION_ID,
354 'value' => $subscription_id,
355 ),
356 ),
357 )
358 );
359
360 if ( empty( $order_ids ) ) {
361 return 0;
362 }
363
364 return absint( $order_ids[0] );
365 }*/
366
367 /**
368 * Persist shared subscription meta onto a parent order.
369 *
370 * This keeps gateway/provider identifiers available for future lookups
371 * and reconciliation.
372 *
373 * @param int $order_id
374 * @param array $event Normalized event payload.
375 *
376 * @return void
377 * @deprecated 4.3.8
378 */
379 /*public function sync_subscription_meta( int $order_id, array $event ) {
380
381 if ( ! empty( $event['subscription_id'] ) ) {
382 update_post_meta( $order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_ID, sanitize_text_field( (string) $event['subscription_id'] ) );
383 }
384 if ( ! empty( $event['customer_id'] ) ) {
385 update_post_meta( $order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_CUSTOMER_ID, sanitize_text_field( (string) $event['customer_id'] ) );
386 }
387
388 if ( ! empty( $event['price_id'] ) ) {
389 update_post_meta( $order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_PLAN_ID, sanitize_text_field( (string) $event['price_id'] ) );
390 }
391 if ( ! empty( $event['event_id'] ) ) {
392 update_post_meta( $order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_LAST_EVENT_ID, sanitize_text_field( (string) $event['event_id'] ) );
393 }
394 if ( ! empty( $event['metadata'] ) && is_array( $event['metadata'] ) ) {
395 update_post_meta( $order_id, '_lp_subscription_metadata', $event['metadata'] );
396 }
397 }*/
398
399 /**
400 * Update current subscription status stored on parent order.
401 *
402 * @param int $order_id
403 * @param string $status Normalized status slug (active/past_due/cancelled/...).
404 * @return void
405 * @deprecated 4.3.8
406 */
407 /*public function update_subscription_status( int $order_id, string $status ) {
408
409 update_post_meta( $order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, sanitize_key( $status ) );
410 }*/
411 /**
412 * Mark parent order paid when initial subscription activation succeeds.
413 *
414 * @param LP_Order $order_subscription
415 * @param array $event Normalized event payload.
416 *
417 * @return void
418 * @deprecated 4.3.8
419 */
420 /*protected function mark_parent_payment_completed( LP_Order $order_subscription, array $event ) {
421 $transaction_id = sanitize_text_field( (string) ( $event['transaction_id'] ?? '' ) );
422 if ( ! $order_subscription->is_completed() ) {
423 $order_subscription->payment_complete( $transaction_id );
424 return;
425 }
426
427 // Parent order is already completed (e.g. completed at ACTIVATED). Backfill
428 // transaction id when later webhook provides it.
429 if ( '' !== $transaction_id ) {
430 $saved_transaction_id = sanitize_text_field(
431 (string) get_post_meta( $order_subscription->get_id(), '_transaction_id', true )
432 );
433 if ( '' === $saved_transaction_id ) {
434 update_post_meta( $order_subscription->get_id(), '_transaction_id', $transaction_id );
435 }
436 }
437 }*/
438 /**
439 * Create or reuse a renewal child order for a renewal event.
440 *
441 * Idempotency strategy:
442 * - First by renewal_key (strongest provider-derived key).
443 * - Fallback by event_id.
444 *
445 * @param LP_Order $order_subscription
446 * @param array $event Normalized event payload.
447 * @param string $target_status Target order status for renewal result.
448 *
449 * @return LP_Order Existing or newly-created renewal order.
450 * @throws Exception
451 * @deprecated 4.3.8
452 */
453 // public function create_renewal_order( LP_Order $order_subscription, array $event, string $target_status = LP_ORDER_PENDING ): LP_Order {
454 //
455 // $renewal_key = $this->get_renewal_key( $event );
456 // if ( ! empty( $renewal_key ) ) {
457 // $existing = $this->find_renewal_order_by_key( $order_subscription->get_id(), $renewal_key );
458 // if ( $existing ) {
459 // return $existing;
460 // }
461 // }
462 //
463 // $event_id = sanitize_text_field( (string) ( $event['event_id'] ?? '' ) );
464 // if ( ! empty( $event_id ) ) {
465 // $existing = $this->find_renewal_order_by_event( $order_subscription->get_id(), $event_id );
466 // if ( $existing ) {
467 // return $existing;
468 // }
469 // }
470 //
471 // $renewal_total = isset( $event['amount'] ) && (float) $event['amount'] > 0 ? (float) $event['amount'] : (float) $order_subscription->get_total();
472 // $renewal_subtotal = isset( $event['amount'] ) && (float) $event['amount'] > 0 ? (float) $event['amount'] : (float) $order_subscription->get_subtotal();
473 // $renewal_currency = ! empty( $event['currency'] ) ? sanitize_text_field( (string) $event['currency'] ) : $order_subscription->get_currency();
474 //
475 // $order_renew = new LP_Order();
476 // $order_renew->set_parent_id( $order_subscription->get_id() );
477 // $order_renew->set_user_id( $order_subscription->get_user_id() );
478 // $order_renew->set_checkout_email( $order_subscription->get_checkout_email() );
479 // $order_renew->set_status( LP_ORDER_PENDING );
480 // $order_renew->set_created_via( 'subscription' );
481 // $order_renew->set_currency( $renewal_currency );
482 // $order_renew->set_total( $renewal_total );
483 // $order_renew->set_subtotal( $renewal_subtotal );
484 // $order_renew->set_data( 'payment_method', $order_subscription->get_data( 'payment_method', '' ) );
485 // $order_renew->set_data( 'payment_method_title', $order_subscription->get_payment_method_title() );
486 //
487 // $renewal_order_id = $order_renew->save();
488 // if ( empty( $renewal_order_id ) ) {
489 // throw new Exception( __( 'Cannot create renewal order.', 'learnpress' ) );
490 // }
491 //
492 // $this->copy_parent_order_items_to_renewal( $order_subscription, $order_renew );
493 // if ( ! empty( $event_id ) ) {
494 // update_post_meta( $renewal_order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_EVENT_ID, $event_id );
495 // }
496 // if ( ! empty( $event['subscription_id'] ) ) {
497 // update_post_meta( $renewal_order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_ID, sanitize_text_field( (string) $event['subscription_id'] ) );
498 // }
499 //
500 // if ( LP_ORDER_COMPLETED !== $target_status && ! empty( $event['transaction_id'] ) ) {
501 // update_post_meta( $renewal_order_id, '_transaction_id', sanitize_text_field( (string) $event['transaction_id'] ) );
502 // }
503 // if ( ! empty( $renewal_key ) ) {
504 // update_post_meta( $renewal_order_id, LP_Gateway_Abstract::META_SUBSCRIPTION_RENEWAL_KEY, $renewal_key );
505 // }
506 //
507 // if ( LP_ORDER_COMPLETED === $target_status ) {
508 // $order_renew->payment_complete( (string) ( $event['transaction_id'] ?? '' ) );
509 // } else {
510 // $order_renew->update_status( $target_status );
511 // }
512 //
513 // $order_renew->add_note(
514 // sprintf(
515 // /* translators: %s: parent order number */
516 // __( 'Subscription renewal generated from parent order %s.', 'learnpress' ),
517 // $order_subscription->get_order_number()
518 // )
519 // );
520 //
521 // return $order_renew;
522 // }
523
524 /**
525 * Ensure webhook subscription id matches the resolved parent order binding.
526 *
527 * This prevents applying a callback payload to a parent order when provider
528 * identifiers do not match (e.g. billing_agreement_id mismatch on PayPal).
529 *
530 * @param LP_Order|false $order_subscription Resolved subscription order.
531 * @param array $event Normalized event payload.
532 *
533 * @return void
534 * @throws Exception
535 * @deprecated 4.3.8
536 */
537 /*protected function validate_parent_subscription_binding( $order_subscription, array $event ) {
538
539 if ( ! $order_subscription instanceof LP_Order ) {
540 return;
541 }
542
543 $event_subscription_id = sanitize_text_field( (string) ( $event['subscription_id'] ?? '' ) );
544 if ( empty( $event_subscription_id ) ) {
545 return;
546 }
547
548 $saved_subscription_id = sanitize_text_field(
549 (string) get_post_meta( $order_subscription->get_id(), LP_Gateway_Abstract::META_SUBSCRIPTION_ID, true )
550 );
551 if ( ! empty( $saved_subscription_id ) && $saved_subscription_id !== $event_subscription_id ) {
552 throw new Exception( __( 'Subscription id does not match parent order.', 'learnpress' ) );
553 }
554 }*/
555 /**
556 * Find renewal child order by parent id + provider event id.
557 *
558 * @param int $parent_order_id
559 * @param string $event_id
560 *
561 * @return LP_Order|false
562 * @deprecated 4.3.8
563 */
564 /*protected function find_renewal_order_by_event( int $parent_order_id, string $event_id ) {
565 $order_ids = get_posts(
566 array(
567 'post_type' => LP_ORDER_CPT,
568 'post_status' => 'any',
569 'post_parent' => $parent_order_id,
570 'posts_per_page' => 1,
571 'fields' => 'ids',
572 'meta_query' => array(
573 array(
574 'key' => LP_Gateway_Abstract::META_SUBSCRIPTION_EVENT_ID,
575 'value' => $event_id,
576 ),
577 ),
578 )
579 );
580
581 if ( empty( $order_ids ) ) {
582 return false;
583 }
584
585 return learn_press_get_order( absint( $order_ids[0] ) );
586 }*/
587
588 /**
589 * Find renewal child order by parent id + renewal key.
590 *
591 * @param int $parent_order_id
592 * @param string $renewal_key
593 *
594 * @return LP_Order|false
595 * @deprecated 4.3.8
596 */
597 /*protected function find_renewal_order_by_key( int $parent_order_id, string $renewal_key ) {
598 $order_ids = get_posts(
599 array(
600 'post_type' => LP_ORDER_CPT,
601 'post_status' => 'any',
602 'post_parent' => $parent_order_id,
603 'posts_per_page' => 1,
604 'fields' => 'ids',
605 'meta_query' => array(
606 array(
607 'key' => LP_Gateway_Abstract::META_SUBSCRIPTION_RENEWAL_KEY,
608 'value' => $renewal_key,
609 ),
610 ),
611 )
612 );
613
614 if ( empty( $order_ids ) ) {
615 return false;
616 }
617
618 return learn_press_get_order( absint( $order_ids[0] ) );
619 }*/
620
621 /**
622 * Build stable renewal dedupe key from event payload.
623 *
624 * Priority:
625 * - explicit `renewal_key` provided by gateway mapping.
626 * - fallback `subscription_id|transaction_id` composite.
627 *
628 * @param array $event Normalized event payload.
629 *
630 * @return string Non-empty dedupe key when enough data exists.
631 * @deprecated 4.3.8
632 */
633 /*protected function get_renewal_key( array $event ): string {
634 $renewal_key = sanitize_text_field( (string) ( $event['renewal_key'] ?? '' ) );
635 if ( ! empty( $renewal_key ) ) {
636 return $renewal_key;
637 }
638
639 $subscription_id = sanitize_text_field( (string) ( $event['subscription_id'] ?? '' ) );
640 $transaction_id = sanitize_text_field( (string) ( $event['transaction_id'] ?? '' ) );
641 if ( ! empty( $subscription_id ) && ! empty( $transaction_id ) ) {
642 return $subscription_id . '|' . $transaction_id;
643 }
644
645 return '';
646 }*/
647
648 /**
649 * Clone parent order items/meta into a renewal child order.
650 *
651 * Copies quantity/subtotal/total from the parent item meta so renewal
652 * records remain auditable even when catalog prices change later.
653 *
654 * @param LP_Order $order_subscription
655 * @param LP_Order $order_renew
656 *
657 * @return void
658 * @deprecated 4.3.8
659 */
660 /*protected function copy_parent_order_items_to_renewal( LP_Order $order_subscription, LP_Order $order_renew ) {
661 $parent_items = $order_subscription->get_items();
662 if ( empty( $parent_items ) || ! is_array( $parent_items ) ) {
663 return;
664 }
665
666 foreach ( $parent_items as $parent_item ) {
667 $parent_item_id = absint( is_array( $parent_item ) ? ( $parent_item['id'] ?? 0 ) : ( $parent_item->order_item_id ?? 0 ) );
668 $item_id = absint( is_array( $parent_item ) ? ( $parent_item['item_id'] ?? 0 ) : ( $parent_item->item_id ?? 0 ) );
669 $item_type = is_array( $parent_item ) ? ( $parent_item['item_type'] ?? '' ) : ( $parent_item->item_type ?? '' );
670 $item_name = is_array( $parent_item ) ? ( $parent_item['name'] ?? '' ) : ( $parent_item->order_item_name ?? '' );
671
672 if ( empty( $item_id ) || empty( $item_type ) ) {
673 continue;
674 }
675
676 $quantity = 1;
677 $subtotal = 0;
678 $total = 0;
679
680 if ( $parent_item_id > 0 ) {
681 $quantity_meta = learn_press_get_order_item_meta( $parent_item_id, '_quantity', true );
682 $subtotal_meta = learn_press_get_order_item_meta( $parent_item_id, '_subtotal', true );
683 $total_meta = learn_press_get_order_item_meta( $parent_item_id, '_total', true );
684
685 if ( '' !== $quantity_meta ) {
686 $quantity = (float) $quantity_meta;
687 }
688 if ( '' !== $subtotal_meta ) {
689 $subtotal = (float) $subtotal_meta;
690 }
691 if ( '' !== $total_meta ) {
692 $total = (float) $total_meta;
693 }
694 }
695
696 $new_item_id = $order_renew->add_item(
697 array(
698 'item_id' => $item_id,
699 'item_type' => $item_type,
700 'order_item_name' => $item_name ? $item_name : get_the_title( $item_id ),
701 'quantity' => $quantity > 0 ? $quantity : 1,
702 'subtotal' => $subtotal,
703 'total' => $total,
704 )
705 );
706
707 if ( ! $new_item_id ) {
708 continue;
709 }
710 }
711 }*/
712 }
713 }
714