Files

521 lines
18 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class PC_Membership_Stripe {
private static function get_options() {
return get_option( 'pc_membership_options', array() );
}
private static function get_secret_key() {
$options = self::get_options();
$mode = isset( $options['mode'] ) ? $options['mode'] : 'test';
return isset( $options[ $mode . '_secret_key' ] ) ? $options[ $mode . '_secret_key' ] : '';
}
private static function get_publishable_key() {
$options = self::get_options();
$mode = isset( $options['mode'] ) ? $options['mode'] : 'test';
return isset( $options[ $mode . '_publishable_key' ] ) ? $options[ $mode . '_publishable_key' ] : '';
}
private static function get_currency() {
$options = self::get_options();
return isset( $options['currency'] ) ? $options['currency'] : 'usd';
}
public static function init() {
$secret_key = self::get_secret_key();
if ( empty( $secret_key ) ) {
return false;
}
if ( ! class_exists( '\Stripe\Stripe' ) ) {
if ( file_exists( PC_MEMBERSHIP_PLUGIN_DIR . 'vendor/autoload.php' ) ) {
require_once PC_MEMBERSHIP_PLUGIN_DIR . 'vendor/autoload.php';
}
}
if ( class_exists( '\Stripe\Stripe' ) ) {
\Stripe\Stripe::setApiKey( $secret_key );
\Stripe\Stripe::setApiVersion( '2023-10-16' );
return true;
}
return false;
}
public static function create_checkout_session( $plan_id, $user_id = null, $customer_email = null ) {
if ( ! self::init() ) {
return new WP_Error( 'stripe_not_configured', __( 'Stripe is not configured properly.', 'pc-membership-abc123' ) );
}
global $wpdb;
$plan = $wpdb->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();
}
}