admin_name ) { $this->admin_name = preg_replace( '!LP_Gateway_!', '', get_class( $this ) ); }*/ if ( ! $this->id ) { $this->id = sanitize_title( $this->title ); } $this->settings = LP_Settings::instance()->get_group( $this->id ); $this->enabled = $this->settings->get( 'enable', 'no' ); add_filter( 'learn-press/admin/get-settings/admin-options-' . $this->id, array( $this, 'get_settings' ) ); } /** * Return unique Id of payment * * @return null|string */ public function get_id() { return $this->id; } /** * Return method title. * * @return string */ public function get_method_title() { return $this->method_title; } /** * Return method description. * * @return string */ public function get_method_description() { return $this->method_description; } /** * Return method title displays in front end. * * @return string */ public function get_title() { return apply_filters( 'learn_press_gateway_title', $this->title, $this->id ); } /** * Return method description displays in front end. * * @return string */ public function get_description() { return apply_filters( 'learn_press_gateway_description', $this->description, $this->id ); } /** * Payment is turn on or off? * * @return bool */ public function is_enabled() { return $this->enabled === 'yes'; } public function enable( $status ) { if ( is_bool( $status ) ) { $this->enabled = $status; $options = get_option( 'learn_press_' . $this->get_id() ); if ( ! $options ) { $options = array(); } $options['enable'] = $status ? 'yes' : 'no'; update_option( 'learn_press_' . $this->get_id(), $options ); } return $this->enabled == 'yes'; } /** * Process the payment. * * @param $order_id * * @return array */ public function process_payment( $order_id ) { return array(); } /** * Check if order should use subscription flow. * * Integrations decide via filter `learn-press/gateway/subscription-order`. * * @param LP_Order $order * * @return bool * @deprecated 4.3.8 Use is_data_for_payment_subscription() instead. */ /*public function is_subscription_order( LP_Order $order ): bool { $order_id = $order->get_id(); $saved_price_id = sanitize_text_field( (string) get_post_meta( $order_id, self::META_SUBSCRIPTION_PLAN_ID, true ) ); if ( ! empty( $saved_price_id ) ) { return true; } $is_subscription = (bool) apply_filters( 'learn-press/gateway/subscription-order', false, $order, $this ); return $is_subscription; }*/ /** * Get subscription context for provider APIs. * * Returns a gateway-agnostic payload that child gateways can pass to * `pay_subscription()` after optional gateway-specific normalization. * * @param LP_Order $order * * @return array * @deprecated 4.3.8 */ /*public function get_subscription_context( LP_Order $order ): array { $order_id = $order->get_id(); $context = array( 'price_id' => get_post_meta( $order_id, self::META_SUBSCRIPTION_PLAN_ID, true ), 'quantity' => (int) get_post_meta( $order_id, self::META_SUBSCRIPTION_QUANTITY, true ), 'success_url' => $this->get_return_url( $order ), 'cancel_url' => learn_press_get_page_link( 'checkout' ), 'metadata' => array( 'lp_order_id' => (string) $order_id, 'lp_order_key' => (string) $order->get_order_key(), 'lp_gateway' => $this->get_id(), 'lp_user_id' => (string) $order->get_user_id(), 'lp_order_type' => 'subscription', ), ); if ( empty( $context['quantity'] ) ) { $context['quantity'] = 1; } return (array) apply_filters( 'learn-press/gateway/subscription-context', $context, $order, $this ); }*/ /** * Persist subscription identifiers to order before payment execution. * * This allows payment methods to operate only on normalized parameters and * avoid re-detecting custom integration attributes inside gateway code. * * @param LP_Order $order * @param array $data * * @return void * @deprecated 4.3.8 */ /*protected function persist_subscription_payment_identifiers( LP_Order $order, array $data ) { $order_id = $order->get_id(); update_post_meta( $order_id, self::META_SUBSCRIPTION_PLAN_ID, sanitize_text_field( (string) ( $data['price_id'] ?? '' ) ) ); update_post_meta( $order_id, self::META_SUBSCRIPTION_QUANTITY, max( 1, absint( $data['quantity'] ?? 1 ) ) ); }*/ /** * Resolve normalized subscription payment params from order context. * * Behavior: * - If order contains persisted subscription identifiers, treat it as * subscription payment. * - If order is marked subscription by integration filter but has no * `price_id`, return a validation error early. * - Otherwise return empty array (one-time payment flow). * * @param LP_Order $order * * @return array * @throws Exception * @deprecated 4.3.8 Use is_data_for_payment_subscription() instead. */ /*public function resolve_subscription_payment_data( LP_Order $order ): array { $context = $this->get_subscription_context( $order ); $context = wp_parse_args( $context, array( 'price_id' => '', 'quantity' => 1, 'success_url' => '', 'cancel_url' => '', 'metadata' => array(), ) ); $context['price_id'] = sanitize_text_field( (string) $context['price_id'] ); $context['quantity'] = max( 1, absint( $context['quantity'] ) ); $context['metadata'] = is_array( $context['metadata'] ) ? $context['metadata'] : array(); if ( ! empty( $context['price_id'] ) ) { $this->persist_subscription_payment_identifiers( $order, $context ); return $context; } if ( $this->is_subscription_order( $order ) ) { throw new Exception( __( 'Missing subscription price id.', 'learnpress' ) ); } return array(); }*/ /** * Check data is type payment for subscription * If LP order has data plan id, return data subscription * * @return false|array * @since 4.3.8 * @version 1.0.0 */ public function is_data_for_payment_subscription( LP_Order $lp_order ) { $data_subscription = [ 'plan_id' => '', 'success_url' => $this->get_return_url( $lp_order ), 'cancel_url' => LP_Helper::get_link_no_cache( learn_press_get_page_link( 'checkout' ) ), ]; // Check LP order has data plan id $plan_id = get_post_meta( $lp_order->get_id(), self::META_SUBSCRIPTION_PLAN_ID, true ); if ( empty( $plan_id ) ) { return false; } $data_subscription['plan_id'] = $plan_id; return apply_filters( 'learn-press/gateway/subscription-payment-data', $data_subscription, $lp_order, $this ); } /** * Normalize and validate the shared subscription checkout payload. * * This method is intentionally gateway-agnostic and is used by child gateways * (e.g. Stripe/PayPal) before they build provider-specific API requests. * * Payload contract: * - `price_id` (string, required): provider-side configured recurring price/plan id. * - `quantity` (int): defaults to 1 when missing/invalid/zero. * - `success_url` / `cancel_url` (string, required): absolute callback URLs. * - `metadata` (array): optional identifiers (order/user/etc.) for reconciliation. * * @param array $data * * @return array Normalized payload array. * @throws Exception * @deprecated 4.3.8 */ /*protected function validate_subscription_payload( array $data ): array { // Apply safe defaults to guarantee a stable input shape. $data = wp_parse_args( $data, array( 'price_id' => '', 'quantity' => 1, 'success_url' => '', 'cancel_url' => '', 'metadata' => array(), ) ); // Scalar sanitation/coercion for fields commonly coming from request context. $data['price_id'] = sanitize_text_field( wp_unslash( (string) $data['price_id'] ) ); $data['quantity'] = absint( $data['quantity'] ); if ( empty( $data['quantity'] ) ) { $data['quantity'] = 1; } // URLs are stored in raw form for outbound provider API requests. $data['success_url'] = esc_url_raw( (string) $data['success_url'] ); $data['cancel_url'] = esc_url_raw( (string) $data['cancel_url'] ); // Defensive normalization for optional structured fields. $data['metadata'] = is_array( $data['metadata'] ) ? $data['metadata'] : array(); // price_id is the minimum provider binding required for subscription checkout. if ( empty( $data['price_id'] ) ) { throw new Exception( __( 'Missing subscription price id.', 'learnpress' ) ); } // Redirect URLs are mandatory for provider-hosted checkout flows. if ( empty( $data['success_url'] ) || empty( $data['cancel_url'] ) ) { throw new Exception( __( 'Missing subscription return URLs.', 'learnpress' ) ); } return $data; }*/ /** * Generic subscription checkout flow. * * Child gateways should override this method and return a payload with at * least `status` and `redirect_url` on success. * * @param array $data * * @return array * @throws Exception */ /*public function pay_subscription( array $data ): array { throw new Exception( sprintf( __( 'Gateway %s does not support subscription payment.', 'learnpress' ), $this->get_id() ) ); }*/ /** * Generic subscription checkout flow. * * Child gateways should override this method and return a payload with at * least `status` and `redirect_url` on success. * $data required key: plan_id * * @param LP_Order $lp_order * @param array $data * * @return array * @throws Exception * @since 4.3.8 * @version 1.0.0 */ public function pay_via_subscription( LP_Order $lp_order, array $data ): array { throw new Exception( sprintf( __( 'Gateway %s does not support subscription payment.', 'learnpress' ), $this->get_id() ) ); } /** * Normalize and validate shared plan-creation payload. * * Common payload contract: * - `name` (string, required when `product_id` is empty) * - `amount` (float, required, > 0) * - `currency` (string, required) * - `interval` (day|week|month|year) * - `interval_count` (int, default 1) * - `setup_fee` (float, optional, >= 0) * - `product_id` (string, optional) * - `metadata` (array, optional) * * @param array $data * * @return array * @throws Exception * @since 4.3.7 * @version 1.0.1 */ protected function validate_data_plan_payload( array $data ): array { $data = wp_parse_args( $data, array( 'name' => '', // Name of plan and product (if product_id is empty) 'amount' => 0, 'currency' => learn_press_get_currency(), 'interval' => 'month', 'interval_count' => 1, 'setup_fee' => 0, 'product_id' => '', // if empty, will create product, then create plan with product created 'metadata' => array(), ) ); $data['name'] = LP_Helper::sanitize_params_submitted( $data['name'] ); $data['amount'] = (float) $data['amount']; $data['currency'] = LP_Helper::sanitize_params_submitted( $data['currency'], 'key' ); $data['interval'] = LP_Helper::sanitize_params_submitted( $data['interval'], 'key' ); $data['interval_count'] = max( 1, absint( $data['interval_count'] ) ); $data['setup_fee'] = (float) $data['setup_fee']; $data['product_id'] = LP_Helper::sanitize_params_submitted( $data['product_id'] ); $data['metadata'] = is_array( $data['metadata'] ) ? $data['metadata'] : array(); if ( empty( $data['name'] ) ) { throw new Exception( __( 'Missing subscription plan name.', 'learnpress' ) ); } if ( $data['amount'] <= 0 ) { throw new Exception( __( 'Invalid subscription amount.', 'learnpress' ) ); } if ( $data['setup_fee'] < 0 ) { throw new Exception( __( 'Invalid subscription setup fee.', 'learnpress' ) ); } if ( empty( $data['currency'] ) ) { throw new Exception( __( 'Missing subscription currency.', 'learnpress' ) ); } $allowed_intervals = array( 'day', 'week', 'month', 'year' ); if ( ! in_array( $data['interval'], $allowed_intervals, true ) ) { throw new Exception( __( 'Invalid subscription interval.', 'learnpress' ) ); } return $data; } /** * Create plan of Payment provider. * * @param array $data * * @return array * @throws Exception */ public function create_plan( array $data ): array { throw new Exception( sprintf( __( 'Gateway %s does not support subscription plan creation.', 'learnpress' ), $this->get_id() ) ); } /** * List provider plans/prices with optional filtering/pagination args. * * @param array $args * * @return array * @throws Exception */ public function list_plans( array $args = array() ): array { throw new Exception( sprintf( __( 'Gateway %s does not support listing subscription plans.', 'learnpress' ), $this->get_id() ) ); } /** * Fetch provider plan/price details by plan id. * * Child gateways should override and return at least: * - `plan`: raw provider response * - `summary`: normalized fields used by integrations to compare updates * (amount/currency/interval/interval_count/setup_fee/status) * * @param string $plan_id * * @return array * @throws Exception */ public function get_plan( string $plan_id ): array { throw new Exception( sprintf( __( 'Gateway %s does not support fetching subscription plan.', 'learnpress' ), $this->get_id() ) ); } /** * Update provider plan details by plan id. * * @uses LP_Gateway_Paypal::update_plan * * @param string $plan_id * @param array $data * * @return array * @throws Exception */ public function update_plan( string $plan_id, array $data ): array { throw new Exception( sprintf( __( 'Gateway %s does not support updating subscription plan.', 'learnpress' ), $this->get_id() ) ); } /** * Delete/deactivate provider plan by plan id. * * @param string $plan_id * * @return array * @throws Exception */ public function delete_plan( string $plan_id ): array { throw new Exception( sprintf( __( 'Gateway %s does not support deleting subscription plan.', 'learnpress' ), $this->get_id() ) ); } /** * Generic subscription webhook listener. * * Child gateways should override and orchestrate: * verify -> normalize -> manager dispatch. * * @param WP_REST_Request $request * * @return array * @throws Exception * @deprecated 4.3.8 Use capture_subscription_webhook instead. */ /*public function listen_webhook_subscription( WP_REST_Request $request ): array { throw new Exception( sprintf( __( 'Gateway %s does not support subscription webhook.', 'learnpress' ), $this->get_id() ) ); }*/ /** * Receive subscription webhook from provider. * * @throws Exception * * @since 4.3.7 * @version 1.0.0 */ public function capture_subscription_webhook( WP_REST_Request $request ) { throw new Exception( sprintf( __( 'Gateway %s does not support subscription webhook.', 'learnpress' ), $this->get_id() ) ); } /** * Verify subscription webhook payload/signature with provider. * * Child gateways should return verified provider event payload/object. * * @param array $webhook_data Generic webhook data extracted from transport layer. * * @return array|object * @throws Exception * @deprecated 4.3.8 */ /*public function verify_subscription_webhook( array $webhook_data ) { throw new Exception( sprintf( __( 'Gateway %s does not support subscription webhook verification.', 'learnpress' ), $this->get_id() ) ); }*/ /** * Build normalized webhook data array from transport-specific REST request. * * Contract: * - raw_body: raw payload string (for signature verification like Stripe). * - body: decoded JSON array when $decode_body is true, otherwise null. * - headers: map of required header keys (lowercase) to raw header values. * * @param WP_REST_Request $request * @param array $required_headers * @param bool $decode_body * * @return array * @deprecated 4.3.8 */ /*protected function build_webhook_data_from_request( WP_REST_Request $request, array $required_headers = array(), bool $decode_body = true ): array { $raw_body = (string) $request->get_body(); $headers = array(); foreach ( $required_headers as $required_header ) { $required_header = strtolower( sanitize_key( (string) $required_header ) ); $headers[ $required_header ] = (string) $request->get_header( $required_header ); } $body = null; if ( $decode_body ) { $body = LP_Helper::json_decode( $raw_body, true ); } return array( 'raw_body' => $raw_body, 'body' => is_array( $body ) ? $body : null, 'headers' => $headers, ); }*/ /** * Validate normalized webhook payload contract before provider verification. * * This centralizes fail-fast checks so each gateway does not re-implement * required key/header validation and accidentally diverge key names. * * @param array $webhook_data * @param array $required_top_level_keys Allowed: raw_body, body, headers. * @param array $required_headers * * @return void * @throws Exception * @deprecated 4.3.8 */ // protected function validate_webhook_data_contract( array $webhook_data, array $required_top_level_keys = array(), array $required_headers = array() ) { // // $missing = array(); // // foreach ( $required_top_level_keys as $required_key ) { // $required_key = sanitize_key( (string) $required_key ); // // switch ( $required_key ) { // case 'raw_body': // if ( empty( $webhook_data['raw_body'] ) || ! is_string( $webhook_data['raw_body'] ) ) { // $missing[] = 'raw_body'; // } // break; // case 'body': // if ( empty( $webhook_data['body'] ) || ! is_array( $webhook_data['body'] ) ) { // $missing[] = 'body'; // } // break; // case 'headers': // if ( ! isset( $webhook_data['headers'] ) || ! is_array( $webhook_data['headers'] ) ) { // $missing[] = 'headers'; // } // break; // } // } // // $headers_map = is_array( $webhook_data['headers'] ?? null ) ? $webhook_data['headers'] : array(); // foreach ( $required_headers as $required_header ) { // $required_header = strtolower( sanitize_key( (string) $required_header ) ); // $header_value = sanitize_text_field( (string) ( $headers_map[ $required_header ] ?? '' ) ); // if ( '' === $header_value ) { // $missing[] = 'headers.' . $required_header; // } // } // // if ( ! empty( $missing ) ) { // throw new Exception( // sprintf( // /* translators: %s: comma separated required webhook fields. */ // __( 'Invalid webhook request data: missing %s.', 'learnpress' ), // implode( ', ', array_unique( $missing ) ) // ), // 400 // ); // } // } /** * Normalize provider webhook event to LP event payload. * * Child gateways should map provider-specific event types/fields into this * canonical schema so Subscription Manager can process consistently. * * @param array|object $provider_event * * @return array * @deprecated 4.3.8 Use normalize_subscription_data instead. */ /*public function normalize_subscription_event( $provider_event ): array { $event = array( 'event_id' => '', 'event_type' => '', 'subscription_id' => '', 'customer_id' => '', 'price_id' => '', 'parent_order_id' => 0, 'transaction_id' => '', 'amount' => 0, 'currency' => '', 'status' => '', 'metadata' => array(), 'raw' => $provider_event, ); return (array) apply_filters( 'learn-press/gateway/subscription/event', $event, $provider_event, $this ); }*/ /** * Define key of system LearnPress for webhook data. * * @param array $webhook_data [lp_order_id, plan_id, subscription_id, subscription_status] * * @return void */ final public function normalize_subscription_data( array &$webhook_data = [] ) { $webhook_data = array_merge( array( 'lp_order_id' => 0, 'lp_plan_id' => '', // Plan id of payment, not membership plan id. 'lp_subscription_id' => '', // Subscription id of payment plan id. 'lp_subscription_status' => '', // Subscription status of payment plan id. ), $webhook_data ); } /** * Process subscription by status. * * Status flow: * - trial/activated: triggers once on the parent LP Order (first payment). * - renewed: triggers on each renewal, creating a child LP Order. * - created/expired/canceled/suspended: logged, do_action only, no order state change. * * @param LP_Order $lp_order * @param string $lp_subscription_status_set_to_handle Status you want set to handle by case * @param array $webhook_data * * @return void * @throws Exception * @since 4.3.7 * @version 1.0.0 */ final public function process_subscription_by_status( $lp_order, string $lp_subscription_status_set_to_handle, array $webhook_data = [] ) { LP_Debug::log_to_comment( 'Progress wit data: ' . json_encode( $webhook_data, JSON_UNESCAPED_UNICODE ) ); switch ( $lp_subscription_status_set_to_handle ) { case LP_Subscription_Manager::STATUS_TRIAL: // For trial, update LP order to complete, set subscription status to trial $this->process_subscription_when_payment_first( $lp_order, LP_Subscription_Manager::STATUS_TRIAL, $webhook_data ); // Set user is using plan trial $order_user_ids = $lp_order->get_users(); $plan_id = get_post_meta( $lp_order->get_id(), self::META_SUBSCRIPTION_PLAN_ID, true ); foreach ( $order_user_ids as $user_id ) { update_user_meta( $user_id, 'user_plan_trial', $plan_id ); } $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Started trialing created at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( '%s %s', __( 'Next billing time', 'learnpress' ), $webhook_data['next_billing_time'] ?? '' ), sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/trial', $this, $lp_order, $webhook_data ); break; case LP_Subscription_Manager::STATUS_ACTIVATED: // For payment plan first success, update LP order to complete, set subscription status to activated $this->process_subscription_when_payment_first( $lp_order, $lp_subscription_status_set_to_handle, $webhook_data ); $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Activated created at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( '%s: %s', __( 'Next billing time', 'learnpress' ), $webhook_data['next_billing_time'] ?? '' ), sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/active', $this, $lp_order, $webhook_data ); break; case LP_Subscription_Manager::STATUS_RENEWED: // For payment plan renew success, parent order is completed and payment renew success $this->process_subscription_when_payment_renew_success( $lp_order, $webhook_data ); $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Renew created at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( '%s: %s', __( 'Next billing time', 'learnpress' ), $webhook_data['next_billing_time'] ?? '' ), sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/renew', $this, $lp_order, $webhook_data ); break; case LP_Subscription_Manager::STATUS_EXPIRED: // For payment plan expired, not impact orders $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Expired created at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/expired', $this, $lp_order, $webhook_data ); break; case LP_Subscription_Manager::STATUS_SUSPENDED: // For payment plan suspended, not impact orders $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Suspended at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/suspended', $this, $lp_order, $webhook_data ); break; case LP_Subscription_Manager::STATUS_CANCELLED: // For payment plan canceled, not impact orders $lp_order->add_note( sprintf( 'LP Order: %s %s: %s. %s, %s', sprintf( '%s', $lp_order->get_edit_link(), $lp_order->get_order_number() ), __( 'Canceled at', 'learnpress' ), $webhook_data['create_time'] ?? '', sprintf( __( 'Subscription ID: %s', 'learnpress' ), $webhook_data['lp_subscription_id'] ), sprintf( __( 'Plan ID: %s', 'learnpress' ), $webhook_data['lp_plan_id'] ) ) ); do_action( 'learn-press/subscription/cancelled', $this, $lp_order, $webhook_data ); break; } do_action( 'learn-press/subscription/process', $this, $lp_order, $webhook_data ); } /** * Process order when subscription payment first. * Update status of order parent to completed * Save META_SUBSCRIPTION_STATUS, lp_subscription_amount, lp_subscription_currency * * @since 4.3.7 * @version 1.0.1 */ private function process_subscription_when_payment_first( LP_Order $lp_order, string $lp_subscription_status_set_to_handle, $webhook_data ) { $lp_subscription_amount = $webhook_data['lp_subscription_amount'] ?? 0; $lp_subscription_currency = $webhook_data['lp_subscription_currency'] ?? ''; $lp_order->update_status( LP_ORDER_COMPLETED ); update_post_meta( $lp_order->get_id(), self::META_SUBSCRIPTION_STATUS, $lp_subscription_status_set_to_handle ); update_post_meta( $lp_order->get_id(), 'lp_subscription_amount', $lp_subscription_amount ); update_post_meta( $lp_order->get_id(), 'lp_subscription_currency', $lp_subscription_currency ); update_post_meta( $lp_order->get_id(), self::META_SUBSCRIPTION_DATA_RECEIVER, wp_json_encode( $webhook_data, JSON_UNESCAPED_UNICODE ) ); do_action( 'learn-press/subscription/order/success', $lp_order, $webhook_data ); } /** * Process order when payment recurring success. * Create new order child * Save META_SUBSCRIPTION_DATA_RECEIVER, lp_subscription_amount, lp_subscription_currency * * @throws Exception * @since 4.3.7 * @version 1.0.1 */ private function process_subscription_when_payment_renew_success( LP_Order $lp_order_parent, $webhook_data ) { $lp_subscription_amount = $webhook_data['lp_subscription_amount'] ?? 0; $lp_subscription_currency = $webhook_data['lp_subscription_currency'] ?? ''; // Create new Order child $order_renew = new LP_Order(); $order_renew->set_parent_id( $lp_order_parent->get_id() ); $order_renew->set_user_id( $lp_order_parent->get_user_id() ); $order_renew->set_checkout_email( $lp_order_parent->get_checkout_email() ); $order_renew->set_status( LP_ORDER_COMPLETED ); $order_renew->set_created_via( 'subscription' ); $order_renew->set_currency( $lp_subscription_currency ?? $lp_order_parent->get_currency() ); $order_renew->set_total( $lp_subscription_amount ); $order_renew->set_subtotal( $lp_subscription_amount ); $order_renew->set_data( 'payment_method', $lp_order_parent->get_data( 'payment_method' ) ); $order_renew->set_data( 'payment_method_title', $lp_order_parent->get_payment_method_title() ); $order_renew->save(); //error_log( 'renew ' . json_encode( $webhook_data, JSON_UNESCAPED_UNICODE ) ); // Add item to order renew foreach ( $lp_order_parent->get_all_items() as $item ) { $item['subtotal'] = $lp_subscription_amount; $item['total'] = $lp_subscription_amount; $order_renew->add_item( $item ); } update_post_meta( $order_renew->get_id(), self::META_SUBSCRIPTION_DATA_RECEIVER, wp_json_encode( $webhook_data, JSON_UNESCAPED_UNICODE ) ); update_post_meta( $order_renew->get_id(), 'lp_subscription_amount', $lp_subscription_amount ); update_post_meta( $order_renew->get_id(), 'lp_subscription_currency', $lp_subscription_currency ); do_action( 'learn-press/subscription/order/renew-success', $order_renew, $webhook_data ); } /** * Get provider manage subscription URL for order. * * Child gateways can override when provider offers customer portal pages. * * @param LP_Order $order * * @return string */ public function get_manage_subscription_url( LP_Order $order ): string { $url = get_post_meta( $order->get_id(), self::META_SUBSCRIPTION_MANAGE_URL, true ); if ( ! is_string( $url ) ) { $url = ''; } return (string) apply_filters( 'learn-press/gateway/subscription/manage-url', $url, $order, $this ); } /** * Get the icon of payment displays in front end. * * @return mixed */ public function get_icon() { $size = apply_filters( 'learn-press/default-payment-gateway-icon-sizes', null ); // array( 52, 32 ) is low quatity. if ( $size ) { $icon_size = sprintf( 'width: %dpx; height: %dpx', $size[0], $size[1] ); } else { $icon_size = ''; } $icon = $this->icon ? '' . esc_attr( $this->get_title() ) . '' : ''; return apply_filters( 'learn_press_gateway_icon', $icon, $this->id ); } /** * Return the form where user can input payment details or anything else. * * @return string */ public function get_payment_form() { return apply_filters( 'learn_press_gateway_payment_form', '', $this ); } /** * Validate required field before submitting fields. * * @return bool */ public function validate_fields() { // TODO: validate fields if needed return true; } /** * @param LP_Order $order * * @return mixed */ public function get_return_url( $order = null ) { if ( $order ) { $return_url = $order->get_checkout_order_received_url(); } else { $return_url = learn_press_get_endpoint_url( 'lp-order-received', '', learn_press_get_page_link( 'checkout' ) ); } return apply_filters( 'learn_press_get_return_url', $return_url, $order ); } /** * @param string $prop * * @deprecated 4.3.9 */ public function __get( $prop ) { _deprecated_function( __METHOD__, '4.3.9' ); return false; switch ( $prop ) { case 'method_title': case 'method_description': case 'id': _deprecated_argument( $prop, '3.0.0', sprintf( __( '%s has been deprecated. Please use % instead of.', 'learnpress' ), $prop, "get_{$prop}" ) ); return call_user_func( array( $this, "get_{$prop}" ) ); default: return property_exists( $this, $prop ) ? $this->{$prop} : false; } } /** * @return string */ public function __toString() { return $this->method_title; } /** * Refund payment * * * @since 4.4.0 * @version 1.0.0 * @return void * @throws Exception */ public function refund( $lp_order, float $amount = 0, string $note = '' ) { throw new Exception( __( 'This gateway does not support refund.', 'learnpress' ) ); } }