|| empty( $source->customer ) || empty( $source->source ) ) { throw new \Exception( __( 'No payment method found for this pre-order.', 'funnelkit-stripe-woo-payment-gateway' ) ); } $response = $this->create_and_confirm_intent_for_off_session_pre_order( $order, $source ); if ( ! $response ) { throw new \Exception( __( 'Failed to create payment intent.', 'funnelkit-stripe-woo-payment-gateway' ) ); } $is_authentication_required = $this->is_authentication_required_for_payment_pre_order( $response ); if ( ! empty( $response->error ) && ! $is_authentication_required ) { if ( ! $retry ) { throw new \Exception( $response->error->message ); } $this->remove_order_source_before_retry( $order ); $this->process_pre_order_release_payment( $order, false ); } elseif ( $is_authentication_required ) { $charge = $this->get_latest_charge_from_intent( $response->error->payment_intent ); $id = $charge->id; $order->set_transaction_id( $id ); /* translators: %s is the charge Id */ $order->update_status( 'failed', sprintf( __( 'Stripe charge awaiting authentication by user: %s.', 'funnelkit-stripe-woo-payment-gateway' ), $id ) ); if ( is_callable( array( $order, 'save' ) ) ) { $order->save(); } \WC_Emails::instance(); do_action( 'fkwcs_gateway_stripe_process_payment_authentication_required', $order ); throw new \Exception( print_r( $response, true ) ); } else { // Successful - get the charge and process the response $charge = $this->get_latest_charge_from_intent( $response ); if ( $charge ) { $this->process_response_pre_order( $charge, $order ); } else { throw new \Exception( __( 'No charge found in payment intent.', 'funnelkit-stripe-woo-payment-gateway' ) ); } } } catch ( \Exception $e ) { Helper::log( 'Pre Order Release Payment Error: ' . $e->getMessage() ); $error_message = $e->getMessage(); /* translators: error message */ $order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'funnelkit-stripe-woo-payment-gateway' ), $error_message ); // Mark order as failed if not already set, // otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times if ( $order && ! $order->has_status( 'failed' ) ) { $order->update_status( 'failed', $order_note ); } elseif ( $order ) { $order->add_order_note( $order_note ); } } } /** * Determines if there is a pre-order in the cart and if it is charged upon release. * * @return bool */ public function is_pre_order_charged_upon_release_in_cart() { try { $pre_order_product = $this->get_pre_order_product_from_cart(); return $pre_order_product && $this->is_pre_order_product_charged_upon_release( $pre_order_product ); } catch ( \Exception $e ) { Helper::log( 'Pre-Order Charged Upon Release in Cart Check Error: ' . $e->getMessage() ); return false; } } /** * Determines if an order contains a pre-order and if it is charged upon release. * * @return bool */ public function has_pre_order_charged_upon_release( $order ) { $pre_order_product = $this->get_pre_order_product_from_order( $order ); return $pre_order_product && $this->is_pre_order_product_charged_upon_release( $pre_order_product ); } /** * Hides the save payment method checkbox when the cart contains a pre-order that is charged upon release. * * @param bool $display_save_option The default value of whether the save payment method checkbox should be displayed. * @return bool Whether the save payment method checkbox should be displayed. */ public function hide_save_payment_for_pre_orders_charged_upon_release( $display_save_option ) { try { // Early return if Pre-Orders plugin is not active if ( ! $this->is_pre_orders_enabled() ) { return $display_save_option; } // This function only sets the display param to false, so if it's already hidden or the cart doesn't contain a pre-order, we don't need to do anything. if ( ! $display_save_option || ! $this->is_pre_order_item_in_cart() ) { return $display_save_option; } // If the cart contains a pre-order that is charged upon release, we hide the save payment method checkbox because the payment method is force saved. if ( $this->is_pre_order_charged_upon_release_in_cart() ) { return false; } return $display_save_option; } catch ( \Exception $e ) { Helper::log( 'Hide Save Payment for Pre-Orders Error: ' . $e->getMessage() ); return $display_save_option; // Return original value on error } } /** * Save source to order * * @param \WC_Order $order * @param object $prepared_source */ public function save_source_to_order_pre_order( $order, $prepared_source ) { try { if ( ! $order ) { throw new \Exception( __( 'Order not found.', 'funnelkit-stripe-woo-payment-gateway' ) ); } if ( ! $prepared_source || empty( $prepared_source->customer ) || empty( $prepared_source->source ) ) { throw new \Exception( __( 'Invalid payment source.', 'funnelkit-stripe-woo-payment-gateway' ) ); } $order->update_meta_data( '_fkwcs_stripe_customer_id', $prepared_source->customer ); $order->update_meta_data( '_fkwcs_stripe_source_id', $prepared_source->source ); $order->save(); } catch ( \Exception $e ) { Helper::log( 'Save Source to Order Error: ' . $e->getMessage() ); throw $e; // Re-throw to be handled by calling method } } /** * Create and confirm intent for off session * * @param \WC_Order $order * @param object $source * @return object */ public function create_and_confirm_intent_for_off_session_pre_order( $order, $source, $previous_error = null ) { try { $request = array( 'payment_method' => $source->source, 'payment_method_types' => $this->get_payment_method_types(), 'amount' => Helper::get_stripe_amount( $order->get_total(), strtolower( $order->get_currency() ) ), 'currency' => strtolower( $order->get_currency() ), 'description' => $this->get_order_description( $order ), 'customer' => $source->customer, 'off_session' => 'true', 'confirm' => 'true', 'confirmation_method' => 'automatic', ); if ( true === \in_array( 'card', $request['payment_method_types'], true ) && Helper::should_customize_statement_descriptor() ) { $request['statement_descriptor_suffix'] = $this->clean_statement_descriptor( Helper::get_gateway_descriptor_suffix( $order ) ); } if ( empty( $source->source ) ) { unset( $request['payment_method'] ); } if ( isset( $source->customer ) ) { $request['customer'] = $source->customer; } $request['metadata'] = $this->add_metadata( $order ); $amount_data = $this->add_amount_details( $order, $request['payment_method_types'][0] ?? 'card' ); if ( ! empty( $amount_data ) ) { $request = array_merge( $request, $amount_data ); } $request = apply_filters( 'fkwcs_payment_intent_data', $request, $order ); $client = $this->get_client(); $response = $client->payment_intents( 'create', array( $request ) ); $obj = $this->handle_client_response( $response ); return $obj; } catch ( \Exception $e ) { Helper::log( 'Create Intent for Off Session Error: ' . $e->getMessage() ); return (object) array( 'error' => (object) array( 'code' => 'api_error', 'message' => $e->getMessage(), ), ); } } /** * Check if authentication is required for payment * * @param object $response * @return bool */ public function is_authentication_required_for_payment_pre_order( $response ) { try { if ( ! $response ) { return false; } return ( ! empty( $response->error ) && 'authentication_required' === $response->error->code ) || ( ! empty( $response->last_payment_error ) && 'authentication_required' === $response->last_payment_error->code ); } catch ( \Exception $e ) { Helper::log( 'Authentication Check Error: ' . $e->getMessage() ); return false; } } /** * Process response * * @param object $response * @param \WC_Order $order */ public function process_response_pre_order( $response, $order ) { try { if ( ! $order ) { throw new \Exception( __( 'Order not found.', 'funnelkit-stripe-woo-payment-gateway' ) ); } if ( ! $response || empty( $response->id ) ) { throw new \Exception( __( 'Invalid payment response.', 'funnelkit-stripe-woo-payment-gateway' ) ); } $order->payment_complete( $response->id ); /* translators: %s: Charge ID */ $order->add_order_note( sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'funnelkit-stripe-woo-payment-gateway' ), $response->id ) ); } catch ( \Exception $e ) { Helper::log( 'Process Response Error: ' . $e->getMessage() ); if ( $order ) { /* translators: %s: Error message */ $order->add_order_note( sprintf( __( 'Payment processing error: %s', 'funnelkit-stripe-woo-payment-gateway' ), $e->getMessage() ) ); } } } }