Files
wticreatorstudio/Modules/Subscription/Http/Controllers/StripeWebhookController.php
Fritz Ramirez 10d0c477c8 Initial commit
2024-02-12 22:54:20 -05:00

498 lines
27 KiB
PHP

<?php
namespace Modules\Subscription\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Subscription\Entities\SubscriptionSetting;
use Modules\Subscription\Entities\CustomerSubscription;
use Modules\Subscription\Entities\UserCredit;
use Modules\Subscription\Entities\UserCreditHistory;
use Modules\Subscription\Entities\SubscriptionPlanPrice;
use Modules\Subscription\Entities\SubscriptionCoupon;
use Modules\Subscription\Entities\SubscriptionCouponRedemption;
use Modules\FrontendCMS\Entities\SubsciptionPaymentInfo;
use Modules\Customer\Services\CustomerService;
use App\Traits\SendMail;
use Carbon\Carbon;
use Exception;
use Log;
class StripeWebhookController extends Controller
{
use SendMail;
protected $stripe;
protected $customerService;
public function __construct(CustomerService $customerService){
$credential = getPaymentInfoViaSellerId(1, 4);
$this->stripe = \Stripe\Stripe::setApiKey(@$credential->perameter_3);
$this->customerService = $customerService;
}
public function webhook() {
// This is your Stripe CLI webhook secret for testing your endpoint locally.
//$endpoint_secret = 'whsec_Gyr6d2lzXXF3goYZVyNyxPWg4ZumlPrb'; // live .app
$endpoint_secret = 'whsec_3EeUyDnEVgmUGLL4wqxXGh6Hp74sqeDz'; // live .com
//$endpoint_secret = 'whsec_8713fc6dfb985a0a56eaa2dce9fa5e836b2480189f49865431a0fd529a6a7087'; // testing
$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = null;
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
$response = $event->data->object;
\Log::debug(json_encode($response));
if($event->type == 'charge.captured' && $response['status'] == 'succeeded' && $response['captured'] == true){
$this->updateCustomerSubscription('active','succeeded', true, $response, $event->type);
} else if($event->type == 'charge.expired'){
$this->updateCustomerSubscription('expired','expired', false, $response, $event->type);
} else if($event->type == 'charge.failed'){
$this->updateCustomerSubscription('inactive','failed', false, $response, $event->type);
} else if($event->type == 'charge.refunded'){
$this->updateCustomerSubscription('inactive','refunded', false, $response, $event->type);
} else if($event->type == 'charge.succeeded' && $response['status'] == 'succeeded' && $response['captured'] == true){
$this->updateCustomerSubscription('active','succeeded', true, $response, $event->type);
} else if($event->type == 'customer.subscription.deleted'){
$this->updateCustomerSubscription('cancelled', 'cancelled', false, $response, $event->type);
} else if($event->type == 'customer.subscription.paused'){
$this->updateCustomerSubscription('paused','paused', false, $response, $event->type);
} else if($event->type == 'customer.subscription.resumed'){
$this->updateCustomerSubscription('active','succeeded', true, $response, $event->type);
} else if($event->type == 'customer.subscription.updated'){
if($response['status'] != 'trialing'){
$status = $response['status'];
$payment_status = $status == 'active' ? 'succeeded' : 'failed';
$paid = $status == 'active' ? true : false;
$this->updateCustomerSubscription($status, $payment_status, $paid, $response, $event->type);
}
} else if($event->type == 'invoice.paid'){
$this->updateCustomerSubscription('active','succeeded', true, $response, $event->type);
} else if($event->type == 'invoice.payment_failed'){
$this->updateCustomerSubscription('inactive','failed', false, $response, $event->type);
} else if($event->type == 'checkout.session.completed') {
if($response->mode == 'setup'){
$checkout_session = \Stripe\Checkout\Session::retrieve([
'id' => $response->id
]);
//lets retrieve setup intent
$setup_intent = \Stripe\SetupIntent::retrieve([
'id' => $checkout_session['setup_intent']
]);
$planPrice = SubscriptionPlanPrice::where('stripe_price_id',$setup_intent['metadata']['subscription_plan_price_id'])->first();
if($planPrice){
//lets create the customer
$customer = \Stripe\Customer::create([
'name' => $setup_intent['metadata']['user_name'],
'email' => $setup_intent['metadata']['user_email'],
'payment_method' => $setup_intent['payment_method'],
'invoice_settings' => [
'default_payment_method' => $setup_intent['payment_method']
]
]);
$customer_id = $setup_intent['metadata']['user_id'];
$subscriptionData = [
'customer' => $customer['id'],
'items' => [
[
'price_data' => [
'unit_amount_decimal' => $planPrice->payment == 'monthly' ? round(($planPrice->price / $planPrice->custom_interval) * 100,2) : round($planPrice->price * 100,2),
'currency' => 'usd',
'product' => $planPrice->plan->stripe_product_id,
'recurring' => [
'interval' => $planPrice->payment == 'monthly' ? 'month' : 'year'
],
]
],
],
//'cancel_at' => Carbon::now()->addMonths($planPrice->custom_interval)->addDays($planPrice->trial_period > 0 ? $planPrice->trial_duration : 7)->timestamp,
'trial_period_days' => $planPrice->trial_period > 0 ? $planPrice->trial_duration : 7
];
if(isset($setup_intent['metadata']['coupon_id'])){
$subscriptionData['coupon'] = $setup_intent['metadata']['coupon_id'];
}
$prorate_discount = (float)$setup_intent['metadata']['prorate_discount'];
if($prorate_discount > 0){
//lets create coupon for the prorate discount
$response = \Stripe\Coupon::create([
'amount_off' => $prorate_discount * 100,
'currency' => getCurrencyCode(),
'name' => 'Prorated Discount',
'applies_to' => [
'products' => [$planPrice->plan->stripe_product_id]
],
'max_redemptions' => 1,
]);
$coupon = new SubscriptionCoupon();
$coupon->fill([
'subscription_plan_price_id' => $planPrice->id,
'stripe_coupon_id' => $response['id'],
'name' => $response['name'],
'code' => $response['id'],
'type' => 'Amount',
'applied_to' => 'subscription',
'payment_mode' => 'Monthly',
'discount' => $prorate_discount,
'duration' => 'Once',
'max_redemptions' => 1,
'active' => 1
]);
$coupon->save();
$subscriptionData['coupon'] = $response['id'];
}
$subscription = \Stripe\Subscription::create($subscriptionData);
if(isset($subscription['id'])){
$customer_subscription = new CustomerSubscription();
$customer_subscription->customer_id = $customer_id;
$customer_subscription->txn_id = $subscription['id'];
$customer_subscription->subscription_plan_price_id = $planPrice->id;
$customer_subscription->payment_mode = $planPrice->payment == 'monthly' ? 'Monthly' :'All';
$customer_subscription->paid_invoice = 0;
$customer_subscription->status = $subscription['status'];
$customer_subscription->payment_status = 'uncaptured';
$customer_subscription->is_paid = false;
$customer_subscription->next_payment_at = $planPrice->billing_type == 'Recurring' ? Carbon::createFromTimestamp($subscription['billing_cycle_anchor'])->format('Y-m-d H:i:s') : null;
$customer_subscription->expired_at = Carbon::createFromTimestamp($subscription['trial_end'])->format('Y-m-d H:i:s');
$customer_subscription->last_payment_date = Carbon::now()->format('Y-m-d');
$customer_subscription->save();
if($subscription['discount'] != null && isset($subscription['discount']['coupon'])){
$coupon = SubscriptionCoupon::where('stripe_coupon_id',$subscription['discount']['coupon']['id'])->first();
if($coupon){
$coupon_redemption = new SubscriptionCouponRedemption;
$coupon_redemption->subscription_coupon_id = $coupon->id;
$coupon_redemption->customer_id = $customer_id;
$coupon_redemption->subscription_id = $customer_subscription->id;
$coupon_redemption->save();
}
}
$user = $customer_subscription->user;
$this->sendSubscriptionPendingApproval($user, $user->company_details['storefront']);
}
} else {
Log::debug('Subscription Plan Price not found: '. $setup_intent['metadata']['subscription_plan_price_id']);
}
} else if($response->mode == 'subscription'){
$checkout_session = \Stripe\Checkout\Session::retrieve([
'id' => $response->id,
'expand' => ['subscription'],
]);
$planPrice = SubscriptionPlanPrice::find($checkout_session['subscription']['metadata']['subscription_plan_id']);
if($planPrice){
$subscription = \Stripe\Subscription::update($checkout_session['subscription']['id'],[
'cancel_at' => Carbon::now()->addMonths($planPrice->custom_interval)->addDays($planPrice->trial_period ? $planPrice->trial_duration : 7)->timestamp
]);
$customer_subscription = new CustomerSubscription();
$customer_subscription->customer_id = $response->client_reference_id;
$customer_subscription->txn_id = $checkout_session['subscription']['id'];
$customer_subscription->subscription_plan_price_id = $planPrice->id;
$customer_subscription->payment_mode = $planPrice->payment == 'monthly' ? 'Monthly' :'All';
$customer_subscription->paid_invoice = 1;
$customer_subscription->status = $checkout_session['subscription']['status'];
$customer_subscription->payment_status = 'uncaptured';
$customer_subscription->is_paid = false;
$customer_subscription->next_payment_at = $planPrice->billing_type == 'Recurring' ? Carbon::createFromTimestamp($checkout_session['subscription']['billing_cycle_anchor'])->format('Y-m-d H:i:s') : null;
$customer_subscription->expired_at = Carbon::createFromTimestamp($checkout_session['subscription']['trial_end'])->format('Y-m-d H:i:s');
$customer_subscription->last_payment_date = Carbon::now()->format('Y-m-d');
$customer_subscription->save();
if($checkout_session['subscription']['discount'] != null && isset($checkout_session['subscription']['discount']['coupon'])){
$coupon = SubscriptionCoupon::where('stripe_coupon_id',$checkout_session['subscription']['discount']['coupon']['id'])->first();
if($coupon){
$coupon_redemption = new SubscriptionCouponRedemption;
$coupon_redemption->subscription_coupon_id = $coupon->id;
$coupon_redemption->customer_id = $response->client_reference_id;
$coupon_redemption->subscription_id = $customer_subscription->id;
$coupon_redemption->save();
}
}
} else {
Log::debug('Subscription Plan Price not found: '. $checkout_session['subscription']['metadata']['subscription_plan_id']);
}
} else {
Log::debug('Mode is not subscription');
}
} else {
echo 'Received unknown event type ' . $event->type;
Log::debug(json_encode($response));
}
http_response_code(200);
}
public function updateCustomerSubscription($status, $payment_status, $paid, $response, $event) {
if($event == 'invoice.paid'){
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$response['subscription'])->first();
if($subscription){
Log::debug('Subscription found, event: '.$event.' id: '.$response['subscription']);
if($subscription->planPrice->billing_type == 'Recurring' && isset($response['lines']['data'][0]['period']['end'])) {
$period_end = $response['lines']['data'][0]['period']['end'];
$subscription->expired_at = Carbon::createFromTimestamp($period_end)->toDateTimeString();
$subscription->next_payment_at = Carbon::createFromTimestamp($period_end)->toDateTimeString();
$subscription->last_payment_date = Carbon::createFromTimestamp($response['effective_at'])->format('Y-m-d');
}
if(isset($response['charge']) && $response['charge'] != null){
$user_credits = UserCredit::firstOrCreate(['user_id' => $subscription->customer_id]);
if($subscription->paid_invoice == 1){
$old_subscription = CustomerSubscription::where('customer_id',$subscription->customer_id)->where('status','cancelled')->where('payment_status','cancelled')->where('paid_invoice','>',1)->latest()->first();
//for flex plan downgrade remove unused credits
if($old_subscription && $subscription->planPrice->payment == 'upfront' && $user_credits->subscription_credits > 0 ){
$before_credits = $this->customerService->credits($user_credits->user_id);
$subscription_credits = $user_credits->subscription_credits - $before_credits['total_available_credits']['subscription'];
$user_credits->subscription_credits = $subscription_credits;
$user_credits->save();
$after_credits = $this->customerService->credits($user_credits->user_id);
$user_credits_history = new UserCreditHistory;
$user_credits_history->user_id = $user_credits->user_id;
$user_credits_history->subscription_credits_from = $before_credits['total_available_credits']['subscription'];
$user_credits_history->subscription_credits_to = $after_credits['total_available_credits']['subscription'];
$user_credits_history->alacarte_credits_from = $before_credits['total_available_credits']['alacarte'];;
$user_credits_history->alacarte_credits_to = $after_credits['total_available_credits']['alacarte'];
$user_credits_history->details = 'Remove unused subscription credits because of downgrade to Flex Plan';
$user_credits_history->save();
}
if($old_subscription){
$paid_months = $old_subscription->paid_invoice;
$plan_credits = $old_subscription->planPrice->total_credits;
$months = $old_subscription->planPrice->custom_interval;
if($old_subscription->planPrice->payment == 'monthly' && $subscription->planPrice->payment == 'monthly'){
$prorate_credits = $subscription->planPrice->total_credits - (( $plan_credits / $months ) * ($months - $paid_months));
$prorate_credits = $prorate_credits >= 0 ? floor($prorate_credits) : -1 * floor(abs($prorate_credits));
$subscription_credits = $user_credits->subscription_credits + $prorate_credits;
$user_credits_history_details = 'Prorated Credits from '.$old_subscription->planPrice->plan->title.' to '.$subscription->planPrice->plan->title;
} else {
$subscription_credits = $user_credits->subscription_credits + $subscription->planPrice->total_credits;
$user_credits_history_details = 'Added Total Plan Credits of '.$subscription->planPrice->plan->title;
}
} else {
$subscription_credits = $user_credits->subscription_credits + $subscription->planPrice->total_credits;
$user_credits_history_details = 'Added Total Plan Credits of '.$subscription->planPrice->plan->title;
}
$before_credits = $this->customerService->credits($user_credits->user_id);
$user_credits->subscription_credits = $subscription_credits;
$user_credits->save();
$after_credits = $this->customerService->credits($user_credits->user_id);
$user_credits_history = new UserCreditHistory;
$user_credits_history->user_id = $user_credits->user_id;
$user_credits_history->subscription_credits_from = $before_credits['total_available_credits']['subscription'];
$user_credits_history->subscription_credits_to = $after_credits['total_available_credits']['subscription'];
$user_credits_history->alacarte_credits_from = $before_credits['total_available_credits']['alacarte'];;
$user_credits_history->alacarte_credits_to = $after_credits['total_available_credits']['alacarte'];
$user_credits_history->details = $user_credits_history_details;
$user_credits_history->save();
} else if($subscription->paid_invoice >= 1){
if($subscription->planPrice->payment == 'monthly'){
//per month
$subscription_credits = $user_credits->subscription_credits + $subscription->planPrice->per_month_credits;
$details = 'Added Monthly Credits';
} else {
//yearly
$subscription_credits = $user_credits->subscription_credits + $subscription->planPrice->total_credits;
$details = 'Added Yearly Credits';
}
$before_credits = $this->customerService->credits($user_credits->user_id);
$user_credits->subscription_credits = $subscription_credits;
$user_credits->save();
$after_credits = $this->customerService->credits($user_credits->user_id);
$user_credits_history = new UserCreditHistory;
$user_credits_history->user_id = $user_credits->user_id;
$user_credits_history->subscription_credits_from = $before_credits['total_available_credits']['subscription'];
$user_credits_history->subscription_credits_to = $after_credits['total_available_credits']['subscription'];
$user_credits_history->alacarte_credits_from = $before_credits['total_available_credits']['alacarte'];;
$user_credits_history->alacarte_credits_to = $after_credits['total_available_credits']['alacarte'];
$user_credits_history->details = $details;
$user_credits_history->save();
}
$subscription->status = $status;
$subscription->payment_status = $payment_status;
$subscription->is_paid = $paid;
if($this->stripe == null){
$credential = getPaymentInfoViaSellerId(1, 4);
$stripe = new \Stripe\StripeClient($credential->perameter_3);
$charge_response = $stripe->charges->retrieve($response['charge'], []);
} else {
$charge_response = $this->stripe->charges->retrieve($response['charge'],[]);
}
if($charge_response && isset($charge_response['amount_captured']) && isset($charge_response['receipt_url'])){
$this->sendInvoicePaid($subscription->user->email, $charge_response['amount_captured'],$charge_response['receipt_url']);
}
$subscription->approved = 1;
}
$subscription->paid_invoice++;
$subscription->save();
} else {
Log::debug('Subscription not found, event: '.$event.' id: '.$response['id']);
}
} else if($event == 'invoice.payment_failed'){
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$response['subscription'])->first();
if($subscription){
$this->sendPaymentFailed($subscription->user->email, $response['number'], $response['hosted_invoice_url']);
$this->sendPaymentFailedForSales($subscription->user->email, $response['number'], $response['hosted_invoice_url'], $response['amount_due']);
if($this->stripe == null){
$credential = getPaymentInfoViaSellerId(1, 4);
$stripe = new \Stripe\StripeClient($credential->perameter_3);
$charge_response = $stripe->charges->retrieve($response['charge'], []);
} else {
$charge_response = $this->stripe->charges->retrieve($response['charge'],[]);
}
if($charge_response && isset($charge_response['failure_message'])){
$subscription->message = $charge_response['failure_message'];
}
$subscription->status = $status;
$subscription->payment_status = $payment_status;
$subscription->is_paid = $paid;
$subscription->save();
}
} else {
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$response['id'])->first();
if($subscription){
if($subscription->planPrice->billing_type == 'Recurring' && isset($response['current_period_end'])) {
$period_end = $response['current_period_end'];
$subscription->expired_at = Carbon::createFromTimestamp($period_end)->toDateTimeString();
$subscription->next_payment_at = Carbon::createFromTimestamp($period_end)->toDateTimeString();
$subscription->last_payment_date = Carbon::createFromTimestamp($response['effective_at'])->format('Y-m-d');
}
$subscription->status = $status;
$subscription->payment_status = $payment_status;
$subscription->is_paid = $paid;
$subscription->save();
if($event == 'customer.subscription.deleted'){
$this->sendSubscriptionCancelled($subscription->user->email, $subscription->planPrice->plan->title);
$this->sendSubscriptionCancelledForSales($subscription->user->email, $subscription->planPrice->plan->title);
} else if($event == 'customer.subscription.updated'){
if($subscription->discount == null && isset($response['discount']) && $response['discount'] != null){
$coupon_data = $response['discount']['coupon'];
$coupon = SubscriptionCoupon::firstOrCreate(
[
'stripe_coupon_id' => $coupon_data['id']
],
[
'subscription_plan_price_id' => [$subscription->planPrice->id],
'name' => $coupon_data['name'],
'code' => $coupon_data['id'],
'type' => $coupon_data['percent_off'] != null ? 'Percentage' : 'Amount',
'applied_to' => 'subscription',
'payment_mode' => 'Monthly',
'discount' => $coupon_data['percent_off'] != null ? $coupon_data['percent_off'] : $coupon_data['amount_off'] / 100,
'duration' => ucfirst($coupon_data['duration']),
'max_redemptions' => $coupon_data['max_redemptions']
]
);
$coupon_redemption = new SubscriptionCouponRedemption;
$coupon_redemption->subscription_coupon_id = $coupon->id;
$coupon_redemption->customer_id = $subscription->customer_id;
$coupon_redemption->subscription_id = $subscription->id;
$coupon_redemption->save();
}
}
} else {
Log::debug('Subscription not found, event: '.$event.' id: '.$response['id']);
}
}
}
}