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

347 lines
16 KiB
PHP

<?php
namespace Modules\Subscription\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Log;
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\SubscriptionPlanPricePaypal;
use Modules\Subscription\Entities\SubscriptionCoupon;
use Modules\Subscription\Entities\SubscriptionCouponRedemption;
use Modules\FrontendCMS\Entities\SubsciptionPaymentInfo;
use Modules\Customer\Services\CustomerService;
use Modules\Subscription\Services\PaypalPaymentService;
use App\Traits\SendMail;
use Carbon\Carbon;
use Exception;
class PaypalWebhookController extends Controller
{
use SendMail;
protected $paypal;
protected $customerService;
public function __construct(CustomerService $customerService){
$this->paypal = new PaypalPaymentService();
$this->customerService = $customerService;
}
protected function isValidWebhook()
{
// get request headers
$headers = apache_request_headers();
Log::info($headers);
// get http payload
$body = file_get_contents('php://input');
$webhook_id = '2HD41882U7087321C'; //live
$data =
$headers['PAYPAL-TRANSMISSION-ID'] . '|' .
$headers['PAYPAL-TRANSMISSION-TIME'] . '|' .
$webhook_id . '|' . crc32($body);
// load certificate and extract public key
$pubKey = openssl_pkey_get_public(file_get_contents($headers['PAYPAL-CERT-URL']));
$key = openssl_pkey_get_details($pubKey)['key'];
// verify data against provided signature
$result = openssl_verify(
$data,
base64_decode($headers['PAYPAL-TRANSMISSION-SIG']),
$key,
'sha256WithRSAEncryption'
);
if ($result == 1) {
// webhook notification is verified
Log::info('webhook verified');
return true;
} elseif ($result == 0) {
// webhook notification is NOT verified
Log::info('webhook not verified');
return false;
} else {
// there was an error verifying this
Log::info('webhook verification error');
return false;
}
}
public function webhook(Request $request) {
// Verify that the webhook is authentic
if (!$this->isValidWebhook()) {
Log::error('Received invalid webhook');
return response('Invalid webhook', 400);
exit();
}
// Get the webhook data from the request
$webhookData = $request->all();
$event_type = $webhookData["event_type"];
// Handle the event based on its type
switch ($event_type) {
case "BILLING.SUBSCRIPTION.ACTIVATED":
$this->updateCustomerSubscription($webhookData);
break;
case "BILLING.SUBSCRIPTION.CREATED":
$this->onSubscriptionCreate($webhookData);
break;
case "BILLING.SUBSCRIPTION.CANCELLED":
$this->updateCustomerSubscription($webhookData);
break;
case "PAYMENT.SALE.COMPLETED":
$this->updateCustomerSubscription($webhookData);
break;
case "BILLING.SUBSCRIPTION.PAYMENT.FAILED":
$this->updateCustomerSubscription($webhookData);
break;
// case "BILLING.SUBSCRIPTION.SUSPENDED":
// $this->onSubscriptionSuspended($webhookData);
// break;
}
}
protected function onSubscriptionCreate(array $webhookData)
{
Log::info($webhookData);
$planPrice = SubscriptionPlanPrice::where('paypal_price_id', $webhookData['resource']['plan_id'])->first();
if(!$planPrice){
//lets try to search on paypal prices
$paypalPlanPrice = SubscriptionPlanPricePaypal::where('paypal_price_id', $webhookData['resource']['plan_id'])->first();
if($paypalPlanPrice){
$planPrice = $paypalPlanPrice->planPrice;
}
}
if($planPrice){
$user_data = json_decode($webhookData['resource']['custom_id']);
if(isset($user_data->user_id)){
$customer_id = (int)$user_data->user_id;
$customer_subscription = new CustomerSubscription();
$customer_subscription->customer_id = $customer_id;
$customer_subscription->txn_id = $webhookData['resource']['id'];
$customer_subscription->payment_method = 'paypal';
$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 = 'waiting for approval';
$customer_subscription->payment_status = 'uncaptured';
$customer_subscription->is_paid = false;
$customer_subscription->next_payment_at = null;
$customer_subscription->expired_at = null;
$customer_subscription->last_payment_date = Carbon::now()->format('Y-m-d');
$customer_subscription->save();
if(isset($user_data->coupon_id)){
$coupon_id = $user_data->coupon_id;
Log::info('Coupon ID: '.$coupon_id);
$coupon = SubscriptionCoupon::where('stripe_coupon_id',$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();
}
}
Log::info('Subscription Created');
}
} else {
Log::info('Subscription Cannot Created, plan ID not found');
}
}
protected function updateCustomerSubscription(array $webhookData) {
Log::info($webhookData);
$event = $webhookData['event_type'];
if($event == 'PAYMENT.SALE.COMPLETED'){
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$webhookData['resource']['billing_agreement_id'])->first();
if($subscription){
$paypal_subscription = $this->paypal->showSubscription($subscription->txn_id);
if($paypal_subscription){
if($subscription->planPrice->billing_type == 'Recurring' && isset($paypal_subscription->billing_info->next_billing_time)) {
$period_end = $paypal_subscription->billing_info->next_billing_time;
$subscription->expired_at = Carbon::parse($period_end)->toDateTimeString();
$subscription->next_payment_at = Carbon::parse($period_end)->toDateTimeString();
$subscription->last_payment_date = Carbon::parse($paypal_subscription->billing_info->last_payment->time)->format('Y-m-d');
}
// first payment
if($paypal_subscription->billing_info->cycle_executions[0]->cycles_completed == 1){
$user_credits = UserCredit::firstOrCreate(['user_id' => $subscription->customer_id]);
$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->paid_invoice++;
$subscription->status = 'active';
$subscription->payment_status = 'succeeded';
$subscription->is_paid = 1;
$subscription->save();
}
} else {
Log::debug('Subscription not found, event: '.$event);
}
} else if($event == 'BILLING.SUBSCRIPTION.ACTIVATED'){
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$webhookData['resource']['id'])->first();
if($subscription){
if($subscription->planPrice->billing_type == 'Recurring' && isset($webhookData['billing_info']['next_billing_time'])) {
$period_end = $webhookData['billing_info']['next_billing_time'];
$subscription->expired_at = Carbon::parse($period_end)->toDateTimeString();
$subscription->next_payment_at = Carbon::parse($period_end)->toDateTimeString();
$subscription->last_payment_date = isset($webhookData['billing_info']['last_payment']['time']) ? Carbon::parse($webhookData['billing_info']['last_payment']['time'])->format('Y-m-d') : null;
$subscription->save();
}
}
} else {
$subscription = CustomerSubscription::with('planPrice')->where('txn_id',$webhookData['resource']['id'])->first();
if($subscription){
if($event == 'BILLING.SUBSCRIPTION.PAYMENT.FAILED'){
$subscription->status = 'inactive';
$subscription->payment_status = 'failed';
$this->sendPaymentFailed($subscription->user->email, null, null);
$this->sendPaymentFailedForSales($subscription->user->email, null, null, $response['amount_due']);
} else if($event == 'BILLING.SUBSCRIPTION.CANCELLED'){
$subscription->status = 'cancelled';
$subscription->payment_status = 'cancelled';
$this->sendSubscriptionCancelled($subscription->user->email, $subscription->planPrice->plan->title);
$this->sendSubscriptionCancelledForSales($subscription->user->email, $subscription->planPrice->plan->title);
}
$subscription->save();
}
}
}
}