id = self::GATEWAY_ID; $this->has_fields = false; $this->method_title = __( 'Better Payment (Stripe)', 'better-payment' ); $this->method_description = __( 'Accept payments through Better Payment\'s existing Stripe integration. Stripe credentials are managed in Better Payment → Settings → Stripe.', 'better-payment' ); $this->supports = array( 'products' ); $this->icon = ''; $this->init_form_fields(); $this->init_settings(); $this->title = $this->get_option( 'title' ); $this->description = $this->get_option( 'description' ); add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, function () { $this->process_admin_options(); } ); add_action( 'woocommerce_receipt_' . $this->id, array( $this, 'receipt_page' ) ); } /** * Minimal settings — deliberately no Stripe credentials here. The * gateway consumes Better Payment's existing Stripe configuration. * * @return void */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable/Disable', 'better-payment' ), 'type' => 'checkbox', 'label' => __( 'Enable Better Payment (Stripe)', 'better-payment' ), 'default' => 'no', ), 'title' => array( 'title' => __( 'Title', 'better-payment' ), 'type' => 'text', 'description' => __( 'The payment method title customers see at checkout.', 'better-payment' ), 'default' => __( 'Better Payment', 'better-payment' ), 'desc_tip' => true, ), 'description' => array( 'title' => __( 'Description', 'better-payment' ), 'type' => 'textarea', 'description' => __( 'The payment method description customers see at checkout.', 'better-payment' ), 'default' => __( 'Pay securely using Stripe via Better Payment.', 'better-payment' ), 'desc_tip' => true, ), ); } /** * URL of the Better Payment Stripe settings screen. * * @return string */ public static function bp_stripe_settings_url() { return admin_url( 'admin.php?page=better-payment-admin&tab=settings§ion=stripe' ); } /** * Settings screen: configuration notice + the standard options table. * * @return void */ public function admin_options() { ?>

get_method_title() ); ?>

get_method_description() ); ?>


generate_settings_html(); ?>
'failure' ); } $keys = StripeService::get_global_keys(); if ( empty( $keys['secret_key'] ) || empty( $keys['public_key'] ) ) { wc_add_notice( __( 'This payment method is not configured. Please choose another payment method.', 'better-payment' ), 'error' ); OrderHandler::log( 'process_payment blocked for order #' . $order->get_id() . ': Stripe keys missing.', 'error' ); return array( 'result' => 'failure' ); } // A $0 subscription order is a free-trial checkout: there is nothing // to charge, but the subscription must still activate — and, when // automatic renewal is on, the card must still be collected (via a // Stripe setup-mode session) so the first payment can be charged // off-session when the trial ends. $is_subscription_order = Subscriptions::order_contains_subscription( $order ); $is_free_subscription_order = $is_subscription_order && (float) $order->get_total() < 0.01; if ( $is_free_subscription_order && ! Subscriptions::should_save_payment_method() ) { // Site-wide manual renewal policy: no card is ever stored, so // there is nothing to send the customer to Stripe for. Complete // the free order and activate the subscription directly; the // first payment is invoiced when the trial ends (the same // manual-renewal path every renewal takes on this policy). $order->add_order_note( __( 'Better Payment: free trial checkout — nothing to charge. Renewal payments will be invoiced for manual payment.', 'better-payment' ) ); $order->payment_complete(); $order->save(); if ( function_exists( 'wc_empty_cart' ) ) { wc_empty_cart(); } OrderHandler::log( 'Free trial order #' . $order->get_id() . ' completed without a Stripe session (manual renewal policy).' ); // The module's own completion contract — activates the // subscription (see Subscriptions::on_order_paid()). do_action( 'better_payment/woocommerce/payment_complete', $order, null ); return array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ), ); } // Same id scheme as every existing Better Payment Stripe surface. $bp_order_id = \Better_Payment\Lite\Classes\Handler::new_order_id( 'stripe' ); $success_url = add_query_arg( array( 'better_payment_stripe_status' => 'success', 'better_payment_stripe_id' => $bp_order_id, ), $this->get_return_url( $order ) ); $stripe_customer_id = ''; if ( $is_free_subscription_order ) { // Setup-mode session: collect + save the card without charging. // The customer object is created up front — a setup session only // attaches the payment method to a customer the caller supplies. $customer = StripeService::create_customer( array( 'email' => $order->get_billing_email(), 'name' => trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ), 'metadata' => array( 'wc_order_id' => (string) $order->get_id() ), ), $keys['secret_key'] ); if ( is_wp_error( $customer ) ) { wc_add_notice( __( 'The payment could not be started. Please try again or choose another payment method.', 'better-payment' ), 'error' ); OrderHandler::log( 'Stripe customer creation failed for free trial order #' . $order->get_id() . ': ' . $customer->get_error_message(), 'error' ); return array( 'result' => 'failure' ); } $stripe_customer_id = (string) $customer->id; $session_request = Subscriptions::build_setup_session_request( array( 'bp_order_id' => $bp_order_id, 'wc_order_id' => $order->get_id(), 'success_url' => $success_url, 'cancel_url' => $order->get_cancel_order_url_raw(), 'customer' => $stripe_customer_id, ) ); } else { $session_request = OrderHandler::build_session_request( array( 'bp_order_id' => $bp_order_id, 'wc_order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'amount' => (float) $order->get_total(), 'currency' => $order->get_currency(), 'success_url' => $success_url, 'cancel_url' => $order->get_cancel_order_url_raw(), 'customer_email' => $order->get_billing_email(), 'customer_name' => trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ), 'site_name' => get_bloginfo( 'name' ), ) ); // Subscription carts additionally ask Stripe to keep the payment // method reusable for off-session renewals (see Subscriptions) — // but only while automatic renewal is enabled site-wide. With a // manual renewal policy the card would be stored without ever being // charged, so it is not stored at all; renewals are invoiced instead. if ( $is_subscription_order && Subscriptions::should_save_payment_method() ) { $session_request = Subscriptions::add_off_session_setup( $session_request ); } } OrderHandler::log( 'Payment started for order #' . $order->get_id() . ' (' . $bp_order_id . ').' ); $session = StripeService::create_checkout_session( $session_request, $keys['secret_key'] ); if ( is_wp_error( $session ) ) { wc_add_notice( __( 'The payment could not be started. Please try again or choose another payment method.', 'better-payment' ), 'error' ); $order->add_order_note( sprintf( /* translators: %s: error message */ __( 'Better Payment: could not create the Stripe Checkout Session — %s', 'better-payment' ), $session->get_error_message() ) ); OrderHandler::log( 'Checkout Session creation failed for order #' . $order->get_id() . ': ' . $session->get_error_message(), 'error' ); return array( 'result' => 'failure' ); } // Persist the transaction through the engine's own writer. $transaction_data = OrderHandler::build_transaction_data( array( 'bp_order_id' => $bp_order_id, 'wc_order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'amount' => (float) $order->get_total(), 'currency' => $order->get_currency(), 'customer_email' => $order->get_billing_email(), 'customer_name' => trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ), ), $session ); if ( $is_free_subscription_order ) { // A setup-mode session reports payment_status // 'no_payment_required' from the moment it is CREATED — before // the customer has entered any card. The row must start 'unpaid' // so the return-side verification // (OrderHandler::verify_setup_return()) is the single-shot flip. $transaction_data['status'] = 'unpaid'; } $transaction_row_id = Handler::payment_create( $transaction_data ); if ( ! $transaction_row_id ) { wc_add_notice( __( 'The payment could not be started. Please try again or choose another payment method.', 'better-payment' ), 'error' ); OrderHandler::log( 'Transaction row insert failed for order #' . $order->get_id() . '.', 'error' ); return array( 'result' => 'failure' ); } // Integration metadata only — no new tables. $order->update_meta_data( '_bp_transaction_id', (string) $transaction_row_id ); $order->update_meta_data( '_bp_payment_id', $bp_order_id ); $order->update_meta_data( '_bp_gateway', 'stripe' ); $order->update_meta_data( '_bp_payment_status', $is_free_subscription_order || empty( $session->payment_status ) ? 'unpaid' : sanitize_text_field( $session->payment_status ) ); if ( '' !== $stripe_customer_id ) { // The customer id is known now; the payment method arrives with // the completed setup session (harvest_payment_method()). $order->update_meta_data( Subscriptions::CUSTOMER_META, sanitize_text_field( $stripe_customer_id ) ); } $order->add_order_note( sprintf( /* translators: 1: Better Payment order id, 2: Stripe Checkout Session id */ __( 'Better Payment: redirecting to Stripe Checkout. Payment ID: %1$s, Session: %2$s.', 'better-payment' ), $bp_order_id, sanitize_text_field( $session->id ) ) ); $order->save(); if ( function_exists( 'wc_empty_cart' ) ) { wc_empty_cart(); } // Newer Stripe API versions return a hosted URL; the pinned legacy // version may not — fall back to the receipt page, which redirects // via Stripe.js exactly like every existing Better Payment surface. $redirect = ! empty( $session->url ) ? $session->url : $order->get_checkout_payment_url( true ); OrderHandler::log( 'Redirecting order #' . $order->get_id() . ' to Stripe Checkout (' . ( ! empty( $session->url ) ? 'hosted url' : 'Stripe.js' ) . ').' ); return array( 'result' => 'success', 'redirect' => $redirect, ); } /** * Receipt page — Stripe.js redirect fallback for sessions without a * hosted URL. Mirrors the redirectToCheckout mechanism the existing * surfaces use. * * @param int $order_id WooCommerce order id. * @return void */ public function receipt_page( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order || ! $order->needs_payment() ) { return; } $row_id = (int) $order->get_meta( '_bp_transaction_id' ); $row = $row_id ? DB::get_transaction( $row_id ) : null; $keys = StripeService::get_global_keys(); if ( empty( $row->obj_id ) || empty( $keys['public_key'] ) ) { echo '

' . esc_html__( 'The payment session could not be found. Please try placing the order again.', 'better-payment' ) . '

'; return; } if ( wp_script_is( 'better-payment-stripe', 'registered' ) ) { wp_enqueue_script( 'better-payment-stripe' ); } else { // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- external SDK, unversioned by design (matches Assets.php). wp_enqueue_script( 'better-payment-stripe', 'https://js.stripe.com/v3/', array(), null, true ); } $inline = sprintf( '(function(){if(typeof Stripe==="undefined"){return;}Stripe(%s).redirectToCheckout({sessionId:%s});})();', wp_json_encode( $keys['public_key'] ), wp_json_encode( $row->obj_id ) ); wp_add_inline_script( 'better-payment-stripe', $inline ); echo '

' . esc_html__( 'Redirecting to secure Stripe Checkout…', 'better-payment' ) . '

'; echo '

' . esc_html__( 'Click here if you are not redirected automatically.', 'better-payment' ) . '

'; } }