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'); } $user_credits = UserCredit::firstOrCreate(['user_id' => $subscription->customer_id]); // first payment if($paypal_subscription->billing_info->cycle_executions[0]->cycles_completed == 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 due to plan migration from '.$old_subscription->planPrice->plan->title.' to '.$subscription->planPrice->plan->title; $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'){ if($old_subscription->planPrice->plan->status){ $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 { $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 due to plan migration from '.$old_subscription->planPrice->plan->title.' to '.$subscription->planPrice->plan->title; $user_credits_history->save(); $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; } } 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['resource']['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['resource']['billing_info']['last_payment']['time']) ? Carbon::parse($webhookData['resource']['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, isset($webhookData['resource']['billing_info']['last_failed_payment']['amount']['value']) ? $webhookData['resource']['billing_info']['last_failed_payment']['amount']['value'] : $subscription->planPrice->price); } 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(); } } } }