get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_plans WHERE id = %d", $plan_id ) ); if ( ! $plan ) { return new WP_Error( 'plan_not_found', __( 'Plan not found.', 'pc-membership-abc123' ) ); } $user = null; $wp_user_id = null; if ( $user_id ) { $wp_user_id = $user_id; } elseif ( is_user_logged_in() ) { $wp_user_id = get_current_user_id(); } $customer_id = null; $session_data = array( 'payment_method_types' => array( 'card' ), 'line_items' => array(), 'success_url' => add_query_arg( array( 'session_id' => '{CHECKOUT_SESSION_ID}', 'plan_id' => $plan_id, ), self::get_success_url() ), 'cancel_url' => self::get_cancel_url(), 'metadata' => array( 'plan_id' => $plan_id, 'wp_user_id' => $wp_user_id ?: 0, ), ); try { if ( $wp_user_id ) { $user = get_userdata( $wp_user_id ); if ( $user ) { $customer_id = self::get_or_create_customer( $user, $wp_user_id ); if ( ! is_wp_error( $customer_id ) ) { $session_data['customer'] = $customer_id; } else { $session_data['customer_email'] = $user->user_email; } } } elseif ( $customer_email ) { $session_data['customer_email'] = $customer_email; } if ( $plan->is_subscription ) { $price_data = array( 'currency' => self::get_currency(), 'product_data' => array( 'name' => $plan->name, 'description' => $plan->description ? substr( $plan->description, 0, 500 ) : '', ), 'recurring' => array( 'interval' => $plan->billing_interval, ), 'unit_amount' => intval( $plan->price * 100 ), ); $session_data['mode'] = 'subscription'; $session_data['line_items'] = array( array( 'price_data' => $price_data, 'quantity' => 1, ), ); if ( $plan->trial_days > 0 ) { $session_data['subscription_data'] = array( 'trial_period_days' => $plan->trial_days, ); } } else { $session_data['mode'] = 'payment'; $session_data['line_items'] = array( array( 'price_data' => array( 'currency' => self::get_currency(), 'product_data' => array( 'name' => $plan->name, 'description' => $plan->description ? substr( $plan->description, 0, 500 ) : '', ), 'unit_amount' => intval( $plan->price * 100 ), ), 'quantity' => 1, ), ); } $session = \Stripe\Checkout\Session::create( $session_data ); return $session; } catch ( \Exception $e ) { return new WP_Error( 'stripe_error', $e->getMessage() ); } } public static function get_or_create_customer( $user, $wp_user_id ) { global $wpdb; $subscription = $wpdb->get_row( $wpdb->prepare( "SELECT stripe_customer_id FROM {$wpdb->prefix}pc_membership_subscriptions WHERE user_id = %d ORDER BY id DESC LIMIT 1", $wp_user_id ) ); if ( $subscription && ! empty( $subscription->stripe_customer_id ) ) { return $subscription->stripe_customer_id; } try { $customer = \Stripe\Customer::create( array( 'email' => $user->user_email, 'name' => $user->display_name, 'metadata' => array( 'wp_user_id' => $wp_user_id, ), ) ); return $customer->id; } catch ( \Exception $e ) { return new WP_Error( 'stripe_customer_error', $e->getMessage() ); } } public static function cancel_subscription( $stripe_subscription_id ) { if ( ! self::init() ) { return new WP_Error( 'stripe_not_configured', __( 'Stripe is not configured properly.', 'pc-membership-abc123' ) ); } try { $subscription = \Stripe\Subscription::retrieve( $stripe_subscription_id ); $subscription->cancel(); return true; } catch ( \Exception $e ) { return new WP_Error( 'stripe_error', $e->getMessage() ); } } public static function create_portal_session() { if ( ! self::init() ) { return new WP_Error( 'stripe_not_configured', __( 'Stripe is not configured properly.', 'pc-membership-abc123' ) ); } if ( ! is_user_logged_in() ) { return new WP_Error( 'not_logged_in', __( 'User must be logged in.', 'pc-membership-abc123' ) ); } global $wpdb; $subscription = $wpdb->get_row( $wpdb->prepare( "SELECT stripe_customer_id FROM {$wpdb->prefix}pc_membership_subscriptions WHERE user_id = %d AND status = 'active' ORDER BY id DESC LIMIT 1", get_current_user_id() ) ); if ( ! $subscription || empty( $subscription->stripe_customer_id ) ) { return new WP_Error( 'no_customer', __( 'No Stripe customer found.', 'pc-membership-abc123' ) ); } try { $session = \Stripe\BillingPortal\Session::create( array( 'customer' => $subscription->stripe_customer_id, 'return_url' => self::get_page_url( 'account' ), ) ); return $session; } catch ( \Exception $e ) { return new WP_Error( 'stripe_error', $e->getMessage() ); } } public static function retrieve_session( $session_id ) { if ( ! self::init() ) { return new WP_Error( 'stripe_not_configured', __( 'Stripe is not configured properly.', 'pc-membership-abc123' ) ); } try { $session = \Stripe\Checkout\Session::retrieve( $session_id ); return $session; } catch ( \Exception $e ) { return new WP_Error( 'stripe_error', $e->getMessage() ); } } public static function handle_webhook() { if ( ! self::init() ) { return; } $options = get_option( 'pc_membership_options' ); $webhook_secret = isset( $options['webhook_secret'] ) ? $options['webhook_secret'] : ''; $payload = file_get_contents( 'php://input' ); $sig_header = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? $_SERVER['HTTP_STRIPE_SIGNATURE'] : ''; if ( empty( $webhook_secret ) || empty( $sig_header ) ) { return; } try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $webhook_secret ); } catch ( \Exception $e ) { return; } self::process_webhook_event( $event ); } private static function process_webhook_event( $event ) { global $wpdb; switch ( $event->type ) { case 'checkout.session.completed': self::handle_checkout_completed( $event->data->object ); break; case 'customer.subscription.created': case 'customer.subscription.updated': self::handle_subscription_updated( $event->data->object ); break; case 'customer.subscription.deleted': self::handle_subscription_deleted( $event->data->object ); break; case 'invoice.payment_succeeded': self::handle_payment_succeeded( $event->data->object ); break; case 'invoice.payment_failed': self::handle_payment_failed( $event->data->object ); break; } } private static function handle_checkout_completed( $session ) { global $wpdb; $plan_id = isset( $session->metadata->plan_id ) ? absint( $session->metadata->plan_id ) : 0; $wp_user_id = isset( $session->metadata->wp_user_id ) ? absint( $session->metadata->wp_user_id ) : 0; $customer_id = $session->customer; $subscription_id = null; if ( empty( $plan_id ) ) { return; } $plan = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_plans WHERE id = %d", $plan_id ) ); if ( ! $plan ) { return; } if ( $plan->is_subscription && ! empty( $session->subscription ) ) { $subscription_id = $session->subscription; try { $stripe_subscription = \Stripe\Subscription::retrieve( $subscription_id ); $period_end = date( 'Y-m-d H:i:s', $stripe_subscription->current_period_end ); } catch ( \Exception $e ) { $period_end = null; } } else { $period_end = null; } if ( $wp_user_id && $customer_id ) { $existing = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}pc_membership_subscriptions WHERE user_id = %d AND plan_id = %d", $wp_user_id, $plan_id ) ); if ( $existing ) { $wpdb->update( $wpdb->prefix . 'pc_membership_subscriptions', array( 'stripe_customer_id' => $customer_id, 'stripe_subscription_id' => $subscription_id ?: '', 'status' => 'active', 'expires_at' => $period_end, ), array( 'id' => $existing ) ); } else { $wpdb->insert( $wpdb->prefix . 'pc_membership_subscriptions', array( 'user_id' => $wp_user_id, 'plan_id' => $plan_id, 'stripe_customer_id' => $customer_id, 'stripe_subscription_id' => $subscription_id ?: '', 'status' => 'active', 'started_at' => current_time( 'mysql' ), 'expires_at' => $period_end, ) ); if ( $plan->role ) { $user = get_userdata( $wp_user_id ); if ( $user ) { $user->set_role( $plan->role ); } } } if ( $session->mode === 'payment' && isset( $session->amount_total ) ) { $wpdb->insert( $wpdb->prefix . 'pc_membership_payments', array( 'user_id' => $wp_user_id, 'plan_id' => $plan_id, 'stripe_payment_intent' => $session->payment_intent ?: '', 'amount' => $session->amount_total / 100, 'currency' => self::get_currency(), 'status' => 'succeeded', ) ); } } } private static function handle_subscription_updated( $subscription ) { global $wpdb; $stripe_sub_id = $subscription->id; $customer_id = $subscription->customer; $status = self::map_stripe_status( $subscription->status ); $period_end = date( 'Y-m-d H:i:s', $subscription->current_period_end ); $existing = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_subscriptions WHERE stripe_subscription_id = %s", $stripe_sub_id ) ); if ( $existing ) { $wpdb->update( $wpdb->prefix . 'pc_membership_subscriptions', array( 'status' => $status, 'expires_at' => $period_end, ), array( 'id' => $existing->id ) ); if ( $status === 'active' ) { $plan = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_plans WHERE id = %d", $existing->plan_id ) ); if ( $plan && $plan->role ) { $user = get_userdata( $existing->user_id ); if ( $user ) { $user->set_role( $plan->role ); } } } } } private static function handle_subscription_deleted( $subscription ) { global $wpdb; $stripe_sub_id = $subscription->id; $existing = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_subscriptions WHERE stripe_subscription_id = %s", $stripe_sub_id ) ); if ( $existing ) { $wpdb->update( $wpdb->prefix . 'pc_membership_subscriptions', array( 'status' => 'cancelled', ), array( 'id' => $existing->id ) ); $user = get_userdata( $existing->user_id ); if ( $user ) { $user->set_role( 'subscriber' ); } } } private static function handle_payment_succeeded( $invoice ) { global $wpdb; if ( empty( $invoice->subscription ) ) { return; } $stripe_sub_id = $invoice->subscription; $subscription = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_subscriptions WHERE stripe_subscription_id = %s", $stripe_sub_id ) ); if ( ! $subscription ) { return; } $wpdb->insert( $wpdb->prefix . 'pc_membership_payments', array( 'user_id' => $subscription->user_id, 'plan_id' => $subscription->plan_id, 'stripe_payment_intent' => $invoice->payment_intent ?: '', 'amount' => $invoice->amount_paid / 100, 'currency' => strtolower( $invoice->currency ), 'status' => 'succeeded', ) ); if ( isset( $invoice->billing_reason ) && $invoice->billing_reason === 'subscription_cycle' ) { $stripe_sub = \Stripe\Subscription::retrieve( $stripe_sub_id ); $period_end = date( 'Y-m-d H:i:s', $stripe_sub->current_period_end ); $wpdb->update( $wpdb->prefix . 'pc_membership_subscriptions', array( 'expires_at' => $period_end, ), array( 'id' => $subscription->id ) ); } } private static function handle_payment_failed( $invoice ) { global $wpdb; if ( empty( $invoice->subscription ) ) { return; } $stripe_sub_id = $invoice->subscription; $subscription = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}pc_membership_subscriptions WHERE stripe_subscription_id = %s", $stripe_sub_id ) ); if ( $subscription ) { $user = get_user_by( 'id', $subscription->user_id ); if ( $user ) { wp_mail( $user->user_email, __( 'Payment Failed', 'pc-membership-abc123' ), __( 'Your membership payment has failed. Please update your payment method to continue your subscription.', 'pc-membership-abc123' ) ); } } } private static function map_stripe_status( $stripe_status ) { $map = array( 'active' => 'active', 'past_due' => 'past_due', 'unpaid' => 'unpaid', 'canceled' => 'cancelled', 'incomplete' => 'pending', 'incomplete_expired' => 'expired', 'trialing' => 'active', 'paused' => 'paused', ); return isset( $map[ $stripe_status ] ) ? $map[ $stripe_status ] : 'pending'; } private static function get_success_url() { $options = get_option( 'pc_membership_options' ); $page_id = isset( $options['success_page_id'] ) ? absint( $options['success_page_id'] ) : 0; return $page_id ? get_permalink( $page_id ) : home_url(); } private static function get_cancel_url() { $options = get_option( 'pc_membership_options' ); $page_id = isset( $options['cancel_page_id'] ) ? absint( $options['cancel_page_id'] ) : 0; return $page_id ? get_permalink( $page_id ) : home_url(); } private static function get_page_url( $page_type ) { $options = get_option( 'pc_membership_options' ); $page_id = isset( $options[ $page_type . '_page_id' ] ) ? absint( $options[ $page_type . '_page_id' ] ) : 0; return $page_id ? get_permalink( $page_id ) : home_url(); } }