fjffk简单的咖啡店科开发2方法2 环滁皆山也。其西南诸峰,林壑尤美,望之蔚然而深秀者,琅琊也。山行六七里,渐闻水声潺潺,而泻出于两峰之间者,酿泉也 ??????????????????????????????? ??????????????/????????????????? ............/................... .........................../... ??????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dsdfTransferService.php000064400000044432152667044740010410 0ustar00where('is_active', 1)->first() ?? abort(404); try { $plans = Plan::where('active', 1)->get(); foreach ($plans as $plan) { self::saveProduct($plan); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { try { $productData = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($productData == null) { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->plan_name = $plan->name; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_NAME; $product->product_id = 'BTP-' . strtoupper(Str::random(13)); $product->price_id = 'Not Needed'; $product->save(); } else { $productData->plan_name = $plan->name; $productData->save(); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveProduct(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribe($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $user = auth()->user(); $currency = Currency::where('id', $gateway->currency)->first()->code; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); } $newDiscountedPrice = $plan->price; if ($coupon) { $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } // $subscription = new Subscriptions(); // $subscription->user_id = $user->id; // $subscription->name = $plan->id; // $subscription->stripe_id = 'TSO-' . strtoupper(Str::random(13)); // $subscription->stripe_status = "bank_preparing"; // $subscription->stripe_price = "Not Needed"; // $subscription->quantity = 1; // $subscription->trial_ends_at = $plan->trial_days != 0 ? \Carbon\Carbon::now()->addDays($plan->trial_days) : null; // $subscription->ends_at = \Carbon\Carbon::now()->addDays(30); // $subscription->plan_id = $plan->id; // $subscription->paid_with = self::$GATEWAY_CODE; // $subscription->save(); // $order_id = $subscription->stripe_id; // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $order_id; // $subscriptionItem->stripe_product = $product->product_id; // $subscriptionItem->stripe_price = "Not Needed"; // $subscriptionItem->quantity = 1; // $subscriptionItem->save(); $order_id = 'TSO-' . strtoupper(Str::random(13)); return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'order_id')); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { $request->validate([ 'proof_image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', ]); $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $proof_image = $request->input('proof_image', null); $user = auth()->user(); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $plan = Plan::find($planID) ?? abort(404); $total = $plan->price; try { DB::beginTransaction(); if ($couponID !== null) { $coupon = checkCouponInRequest($couponID); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } $coupon->usersUsed()->attach(auth()->user()->id); } $total += taxToVal($plan->price, $gateway->tax); $subscription = new Subscriptions; $subscription->user_id = $user->id; $subscription->name = $plan->id; $subscription->stripe_id = $orderID; $subscription->stripe_status = 'bank_waiting'; $subscription->stripe_price = 'Not Needed'; $subscription->quantity = 1; $subscription->trial_ends_at = null; switch ($plan->frequency) { case FrequencyEnum::MONTHLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addMonths(1); break; case FrequencyEnum::YEARLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addYears(1); break; case FrequencyEnum::LIFETIME_MONTHLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addMonths(1); // ends each month but auto renewing without payment reqs $subscription->auto_renewal = 1; break; case FrequencyEnum::LIFETIME_YEARLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addYears(1); // ends each year but auto renewing without payment reqs $subscription->auto_renewal = 1; break; default: $subscription->ends_at = \Carbon\Carbon::now()->addDays(30); break; } $subscription->tax_rate = $gateway->tax; $subscription->tax_value = taxToVal($plan->price, $gateway->tax); $subscription->coupon = $couponID; $subscription->total_amount = $total; $subscription->plan_id = $plan->id; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->save(); $order = new UserOrder; $order->order_id = $orderID; $order->plan_id = $plan->id; $order->user_id = $user->id; $order->payment_type = self::$GATEWAY_CODE; $order->price = $total; $order->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $order->status = 'Waiting'; $order->country = $user->country ?? 'Unknown'; $order->tax_rate = $gateway->tax; $order->tax_value = taxToVal($plan->price, $gateway->tax); $order->save(); $filename = Str::random(20) . '_' . time() . '.' . $request->file('proof_image')?->guessExtension(); $paymentProof = new PaymentProof; $paymentProof->order_id = $orderID; $paymentProof->user_id = $user->id; $paymentProof->plan_id = $planID; $paymentProof->total_amount = $total; $paymentProof->proof_image = $filename; $paymentProof->save(); $request->file('proof_image')?->move(public_path('proofs'), $filename); \App\Models\Usage::getSingle()->updateSalesCount($total); CreateActivity::for($user, __('initiated a subscription approval-awaiting bank transaction.'), $plan->name . ' ' . __('Plan')); } catch (Exception $th) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } DB::commit(); app(\App\Services\Common\MenuService::class)->regenerate(); return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. You will be notified once the payment transaction is accepted.'), 'type' => 'success', ]); } public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $user = auth()->user(); $currency = Currency::where('id', $gateway->currency)->first()->code; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); } $newDiscountedPrice = $plan->price; if ($coupon) { $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } $order_id = 'TPO-' . strtoupper(Str::random(13)); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'order_id')); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $user = auth()->user(); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $plan = Plan::find($planID) ?? abort(404); $total = $plan->price; try { DB::beginTransaction(); if ($couponID !== null) { $coupon = checkCouponInRequest($couponID); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } $coupon->usersUsed()->attach(auth()->user()->id); } $total += taxToVal($plan->price, $gateway->tax); $order = new UserOrder; $order->order_id = $orderID; $order->plan_id = $plan->id; $order->user_id = $user->id; $order->type = 'prepaid'; $order->payment_type = self::$GATEWAY_CODE; $order->price = $total; $order->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $order->status = 'Waiting'; $order->country = $user->country ?? 'Unknown'; $order->tax_rate = $gateway->tax; $order->tax_value = taxToVal($plan->price, $gateway->tax); $order->save(); \App\Models\Usage::getSingle()->updateSalesCount($total); CreateActivity::for($user, __('initiated a prepaid pack approval-awaiting bank transaction.'), $plan->name . ' ' . __('Plan')); } catch (Exception $th) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } DB::commit(); return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. You will be notified once the payment transaction is accepted.'), 'type' => 'success', ]); } public static function getSubscriptionStatus($incomingUserId = null) { if ($incomingUserId != null) { $user = User::where('id', $incomingUserId)->first(); } else { $user = Auth::user(); } $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $sub = getCurrentActiveSubscription($user->id); if ($sub != null) { return true; } return false; } public static function getSubscriptionDaysLeft() { $user = Auth::user(); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $sub = getCurrentActiveSubscription($user->id); if ($sub) { return \Carbon\Carbon::now()->diffInDays($sub->ends_at); } else { Log::error('getSubscriptionDaysLeft()'); return 0; } } public static function subscribeCancel($internalUser = null) { $user = $internalUser ?? Auth::user(); $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { $plan = Plan::where('id', $activeSub->plan_id)->first(); self::creditDecreaseCancelPlan($user, $plan); $activeSub->stripe_status = 'bank_canceled'; $activeSub->save(); CreateActivity::for($user, 'cancelled', $plan->name); if ($internalUser != null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return redirect()->route('dashboard.user.index')->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function checkIfTrial() { // there is no trail in bank transfer return false; } public static function getSubscriptionRenewDate() { $user = Auth::user(); $activeSub = getCurrentActiveSubscription($user->id); return \Carbon\Carbon::parse($activeSub->ends_at)->format('F jS, Y'); } public static function cancelSubscribedPlan($subscription, $planId) { try { $order = UserOrder::where('order_id', $subscription->stripe_id)->first(); /** * @var Plan $plan */ $plan = $order->plan; /** * @var User $user */ $user = $order->user; self::creditDecreaseCancelPlan($user, $plan); $subscription->stripe_status = 'bank_canceled'; $subscription->save(); // sent mail if required here later CreateActivity::for($order->user, __('Subscription canceled due to plan deletion.'), $order->plan->name . ' ' . __('Plan')); return true; } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . ' cancelSubscribedPlan(): ' . $th->getMessage() . "\n------------------------\n"); return false; } } public static function handleWebhook(Request $request) { return response()->json(['success' => true]); } } StripeService.php000064400000161655152667044740010101 0ustar00where('active', 1)->get(); foreach ($plans as $plan) { self::saveProduct($plan); } self::createWebhook(); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); } } /** * User array for stripe * * @throws ApiErrorException */ private static function userUpdates(StripeClient $stripe): array { $stripeCustomers = self::getStripeCustomers($stripe); $users = User::query()->whereNotIn('stripe_id', $stripeCustomers->pluck('id')->toArray())->get(); $userUpdates = []; foreach ($users as $user) { $userUpdates[] = [ 'user' => $user, 'userData' => self::userDataFormatter($user), ]; } return $userUpdates; } /** * Create user data for stripe */ private static function userDataFormatter(User $user): array { return [ 'email' => $user->email, 'name' => $user->name . ' ' . $user->surname, 'phone' => $user->phone, 'address' => [ 'line1' => $user->address, 'postal_code' => $user->postal, ], ]; } /** * Get customers from stripe * * @throws ApiErrorException */ private static function getStripeCustomers(StripeClient $stripe): Collection { $existingCustomerIds = collect(); $cursor = null; do { $parameters = ['limit' => 100]; if ($cursor !== null) { $parameters['starting_after'] = $cursor; } $customers = $stripe->customers->all($parameters); $existingCustomerIds = $existingCustomerIds->merge($customers->data); if ($customers->has_more) { $cursor = $customers->data[count($customers->data) - 1]->id; } else { $cursor = null; } } while ($cursor !== null); return $existingCustomerIds; } public static function getPlansPriceIdsForMigration(): void { try { $clientKey = self::getKey(); $stripe = new StripeClient($clientKey); DB::beginTransaction(); $plans = Plan::query()->where('active', 1)->get(); foreach ($plans as $plan) { $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if (! $product) { continue; } $productId = $product->getAttribute('product_id'); $priceData = $stripe->prices->all([ 'product' => $productId, ]); if (isset($priceData?->data[0])) { $priceId = $priceData->data[0]?->id; $product->price_id = $priceId; $product->save(); } } DB::commit(); } catch (Exception|Throwable $ex) { Log::error(self::$GATEWAY_CODE . '-> getPlansPriceIdsForMigration(): ' . $ex->getMessage()); DB::rollBack(); } } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): void { $clientKey = self::getKey(); $stripe = new StripeClient($clientKey); $user = User::query()->where('old_sys_user_id', $subscription->user_id)->first(); if ($user && ! $user->stripe_id) { $stripeSub = $stripe->subscriptions->retrieve($subscription->stripe_id); if ($stripeSub) { $customerId = $stripeSub->customer; $user->stripe_id = $customerId; $user->save(); } } } public static function saveProduct(Plan $plan): void { $clientKey = self::getKey(); $stripe = new StripeClient($clientKey); $gateway = self::getGateway(); try { DB::beginTransaction(); $price = (int) ($plan->price * 100); $findCurrency = Helper::findCurrencyFromId($gateway?->currency); $currency = $findCurrency->getAttribute('code'); [$product, $oldProductId] = self::createGatewayProducts($plan, $stripe); $data = [ 'unit_amount' => $price, 'currency' => $currency, 'product' => $product->getAttribute('product_id'), ]; // if Subscription price and its not lifetime subscription if ($plan->price != 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { $data['recurring'] = ['interval' => $plan->frequency == FrequencyEnum::MONTHLY->value ? 'month' : 'year']; // check if price exists if ($product->price_id !== null) { // Since stripe api does not allow to update recurring values, we deactivate all prices added to this product before and add a new price object. // Deactivate all prices foreach ($stripe->prices->all(['product' => $product->product_id]) as $oldPrice) { try { $stripe->prices->update($oldPrice->id, ['active' => false]); } catch (Exception $ex) { } } $updatedPrice = $stripe->prices->create($data); // save history and update all plans prices and cancel the plan subs with updateUserData() // --------- start create history for old priceID --------- $history = new OldGatewayProducts; $history->plan_id = $plan->id; $history->plan_name = $plan->name; $history->gateway_code = self::$GATEWAY_CODE; $history->product_id = $product->product_id; $history->old_product_id = $oldProductId; $history->old_price_id = $product->price_id; $history->new_price_id = $updatedPrice->id; $history->status = 'check'; $history->save(); // --------- end create history for old priceID --------- $tmp = self::updateUserData(); } else { $updatedPrice = $stripe->prices->create($data); } $product->price_id = $updatedPrice->id; } else { $product->price_id = 'Not Needed'; } $product->save(); DB::commit(); } catch (Exception $ex) { DB::rollBack(); } } private static function createGatewayProducts(Plan $plan, StripeClient $stripe): array { $oldProductId = null; // check if product exists $product = GatewayProducts::query() ->where([ 'plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE, ]) ->first(); $newProduct = $stripe->products->create(['name' => $plan->name]); if ($product !== null) { if ($product->product_id !== null && $plan->name !== null) { $oldProductId = $product->product_id; } } else { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_NAME; } $product->product_id = $newProduct->id; $product->plan_name = $plan->name; $product->save(); return [$product, $oldProductId]; } public static function subscribe($plan) { $gateway = self::getGateway(); try { DB::beginTransaction(); $exception = null; $key = self::getKey($gateway); Stripe::setApiKey($key); $stripe = new StripeClient($key); $user = auth()->user(); try { if ($user->stripe_id != null) { // Check if the customer already exists in Stripe $existingCustomer = $stripe->customers->retrieve($user->stripe_id); } else { // Customer doesn't exist, create a new customer $userData = self::userDataFormatter($user); $stripeCustomer = $stripe->customers->create($userData); $user->stripe_id = $stripeCustomer->id; $user->save(); } } catch (\Stripe\Exception\InvalidRequestException $e) { // Customer doesn't exist, create a new customer $userData = self::userDataFormatter($user); $stripeCustomer = $stripe->customers->create($userData); $user->stripe_id = $stripeCustomer->id; $user->save(); } $currency = Helper::findCurrencyFromId($gateway?->currency)->code; $taxRate = $gateway->tax; $tax_rate_id = null; $taxValue = taxToVal($plan->price, $taxRate); if ($taxRate > 0 && $gateway['automate_tax'] == 0) { try { $stripe_taxs = $stripe->taxRates->all(); foreach ($stripe_taxs as $s_tax) { if ($s_tax->percentage == $taxRate) { $tax_rate_id = $s_tax->id; break; } } if ($tax_rate_id == null) { $new_tax = $stripe->taxRates->create([ 'percentage' => $taxRate, 'display_name' => Str::random(13), 'inclusive' => false, ]); $tax_rate_id = $new_tax->id ?? null; } } catch (\Stripe\Exception\InvalidRequestException $e) { $new_tax = $stripe->taxRates->create([ 'percentage' => $taxRate, 'display_name' => Str::random(13), 'inclusive' => false, ]); $tax_rate_id = $new_tax->id ?? null; } } $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { $exception = __('Stripe product is not defined! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } if ($product->price_id == null) { $exception = __('Stripe product ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } $paymentIntent = null; $price_id_product = $product->price_id; $newDiscountedPrice = $plan->price; $newDiscountedPriceCents = $plan->price * 100; // if the plan lifetime plan. if ($plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value || $plan->frequency == FrequencyEnum::LIFETIME_YEARLY->value) { if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); $newDiscountedPriceCents = (int) (((float) $newDiscountedPrice) * 100); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } // Create the subscription with the customer ID, price ID, and necessary options. $subscription = new Subscriptions; $subscription->user_id = $user->id; $subscription->name = $plan->id; $subscription->stripe_id = 'SLS-' . strtoupper(Str::random(13)); $subscription->stripe_status = 'AwaitingPayment'; // $plan->trial_days != 0 ? "trialing" : "AwaitingPayment"; $subscription->stripe_price = $price_id_product; $subscription->quantity = 1; $subscription->trial_ends_at = null; $subscription->ends_at = $plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value ? Carbon::now()->addMonths(1) : Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->plan_id = $plan->id; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->save(); // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $subscription->stripe_id; // $subscriptionItem->stripe_product = $product->product_id; // $subscriptionItem->stripe_price = $price_id_product; // $subscriptionItem->quantity = 1; // $subscriptionItem->save(); if ($gateway['automate_tax'] === 1) { Cashier::calculateTaxes(); $session = Session::create([ 'customer' => $user->stripe_id, 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => $currency, 'product_data' => [ 'name' => $plan->name, ], 'unit_amount' => $plan->price * 100, ], 'quantity' => 1, ]], 'mode' => 'payment', 'automatic_tax' => [ 'enabled' => true, ], 'metadata' => [ 'product_id' => $product->product_id, 'price_id' => $product->price_id, 'plan_id' => $plan->id, ], 'success_url' => url("webhooks/stripe/{$subscription->id}/success"), 'cancel_url' => url("webhooks/stripe/{$subscription->id}/cancel"), ]); $subscription->stripe_id = $session->id; $subscription->save(); DB::commit(); return redirect($session->url); } $paymentIntent = PaymentIntent::create([ 'amount' => $newDiscountedPriceCents, 'currency' => $currency, 'description' => 'AI Services', 'automatic_payment_methods' => [ 'enabled' => true, ], 'metadata' => [ 'product_id' => $product->product_id, 'price_id' => $product->price_id, 'plan_id' => $plan->id, ], ]); } else { $subscriptionInfo = [ 'customer' => $user->stripe_id, 'items' => [ [ 'price' => $price_id_product, 'tax_rates' => $tax_rate_id ? [$tax_rate_id] : [], ], ], 'payment_behavior' => 'default_incomplete', 'payment_settings' => ['save_default_payment_method' => 'on_subscription'], 'expand' => ['latest_invoice.payment_intent'], 'metadata' => [ 'product_id' => $product->product_id, 'price_id' => $price_id_product, 'plan_id' => $plan->id, ], ]; if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); $newDiscountedPriceCents = (int) (((float) $newDiscountedPrice) * 100); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } $durationMap = [ 'first_month' => ['duration' => 'once'], 'first_year' => ['duration' => 'repeating', 'duration_in_months' => 12], 'all_time' => ['duration' => 'forever'], ]; $durationData = $durationMap[$coupon->duration] ?? ['duration' => 'once']; $data = array_merge( ['percent_off' => $coupon->discount], $durationData ); // search for exist coupon with same percentage created before in stripe then use it, else create new one. $new_coupon try { $new_coupon = null; $stripe_coupons = $stripe->coupons->all()?->data; foreach ($stripe_coupons ?? [] as $s_coupon) { if ($s_coupon->percent_off == $coupon->discount) { $new_coupon = $s_coupon; break; } } if ($new_coupon == null) { $new_coupon = $stripe->coupons->create($data); } } catch (\Stripe\Exception\InvalidRequestException $e) { $new_coupon = $stripe->coupons->create($data); } $subscriptionInfo['coupon'] = $new_coupon->id ?? null; } if ($plan->trial_days != 0) { $trialEndTimestamp = Carbon::now()->addDays($plan->trial_days)->timestamp; $subscriptionInfo += [ 'trial_end' => strval($trialEndTimestamp), 'billing_cycle_anchor' => strval($trialEndTimestamp), ]; } $subscription = new ModelSubscription; $subscription->user_id = $user->id; $subscription->name = $plan->id; $subscription->stripe_id = 'SLS-' . strtoupper(Str::random(13)); $subscription->stripe_status = 'AwaitingPayment'; // $plan->trial_days != 0 ? "trialing" : "AwaitingPayment"; $subscription->stripe_price = $price_id_product; $subscription->quantity = 1; $subscription->trial_ends_at = $plan->trial_days != 0 ? Carbon::now()->addDays($plan->trial_days) : null; $subscription->ends_at = $plan->trial_days != 0 ? Carbon::now()->addDays($plan->trial_days) : Carbon::now()->addDays(30); $subscription->plan_id = $plan->id; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->save(); if ($gateway['automate_tax'] == 1) { Cashier::calculateTaxes(); $dataSubscription = Auth::user() ->newSubscription('default', $price_id_product) ->withMetadata([ 'product_id' => $product->product_id, 'price_id' => $product->price_id, 'plan_id' => $plan->id, ]) ->checkout([ 'success_url' => url("webhooks/stripe/{$subscription->id}/success"), 'cancel_url' => url("webhooks/stripe/{$subscription->id}/cancel"), ]); $newSubscription = $dataSubscription->asStripeCheckoutSession(); $subscription->stripe_id = $newSubscription->id; $subscription->save(); DB::commit(); return redirect($newSubscription->url); } else { $newSubscription = $stripe->subscriptions->create($subscriptionInfo); $subscription->stripe_id = $newSubscription->id; $subscription->save(); } $paymentIntent = [ 'subscription_id' => $newSubscription->id, 'client_secret' => ($plan->trial_days != 0) ? $stripe->setupIntents->retrieve($newSubscription->pending_setup_intent, [])->client_secret : $newSubscription->latest_invoice?->payment_intent?->client_secret, 'trial' => ($plan->trial_days != 0), 'currency' => $currency, 'amount' => $newDiscountedPriceCents, 'description' => 'AI Services', ]; } DB::commit(); return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'paymentIntent', 'product')); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null, ?Subscription $subscription = null) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $settings = Setting::getCache(); $key = self::getKey($gateway); Stripe::setApiKey($key); $user = auth()->user(); $stripe = new StripeClient($key); $couponID = null; $intent = null; $clientSecret = null; if (is_null($subscription)) { if ($referral !== null) { $stripe->customers->update( $user->stripe_id, [ 'metadata' => [ 'referral' => $referral, ], ] ); } $previousRequest = app('request')->create(url()->previous()); $intentType = $request->has('payment_intent') ? 'payment_intent' : ($request->has('setup_intent') ? 'setup_intent' : null); $intentId = $request->input($intentType); $clientSecret = $request->input($intentType . '_client_secret'); $redirectStatus = $request->input('redirect_status'); if ($redirectStatus != 'succeeded') { return back()->with(['message' => __("A problem occurred! $redirectStatus"), 'type' => 'error']); } $intentStripe = $request->has('payment_intent') ? 'paymentIntents' : ($request->has('setup_intent') ? 'setupIntents' : null); $intent = $stripe->{$intentStripe}->retrieve($intentId) ?? abort(404); } try { DB::beginTransaction(); // check validity of the intent if ($subscription || ($intent?->client_secret == $clientSecret && $intent?->status == 'succeeded')) { self::cancelAllSubscriptions(); $subscription = $subscription ?: Subscriptions::where('paid_with', self::$GATEWAY_CODE)->where(['user_id' => $user->id, 'stripe_status' => 'AwaitingPayment'])->latest()->first(); $planId = $subscription->plan_id; $plan = Plan::where('id', $planId)->first(); $total = $plan->price; $currency = Currency::where('id', $gateway->currency)->first()->code; $tax_rate_id = null; $taxValue = taxToVal($plan->price, $gateway->tax); // check the coupon existince if (isset($previousRequest) && $previousRequest->has('coupon')) { $coupon = Coupon::where('code', $previousRequest->input('coupon'))->first(); if ($coupon) { $coupon->usersUsed()->attach(auth()->user()->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $total += $taxValue; // update the subscription to make it active and save the total if ($subscription->auto_renewal) { $subscription->stripe_status = 'stripe_approved'; } else { $subscription->stripe_status = $plan->trial_days != 0 ? 'trialing' : 'active'; } $subscription->tax_rate = $gateway->tax; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $total; $subscription->save(); // save the order $order = new UserOrder; $order->order_id = $subscription->stripe_id; $order->plan_id = $planId; $order->user_id = $user->id; $order->payment_type = self::$GATEWAY_CODE; $order->price = $total; $order->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $order->status = 'Success'; $order->country = Auth::user()->country ?? 'Unknown'; $order->tax_rate = $gateway->tax; $order->tax_value = $taxValue; $order->save(); self::creditIncreaseSubscribePlan($user, $plan); // add plan credits // foreach($waiting_subscriptions as $waitingSubs){ // dispatch(new CancelAwaitingPaymentSubscriptions($stripe, $waitingSubs)); // } // inform the admin CreateActivity::for($user, __('Subscribed to'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); } else { Log::error("StripeController::subscribeCheckout() - Invalid $intentType"); DB::rollBack(); return redirect()->route('dashboard.user.payment.subscription')->with(['message' => __("A problem occurred! $redirectStatus"), 'type' => 'error']); } DB::commit(); if (class_exists('App\Extensions\Affilate\System\Events\AffiliateEvent')) { event(new \App\Extensions\Affilate\System\Events\AffiliateEvent($total, $gateway->currency)); } return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success', ]); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribeCheckout(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } } public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $user = auth()->user(); $key = self::getKey($gateway); Stripe::setApiKey($key); $stripe = new StripeClient($key); try { if ($user->stripe_id != null) { // Check if the customer already exists in Stripe $existingCustomer = $stripe->customers->retrieve($user->stripe_id); } else { // Customer doesn't exist, create a new customer $userData = self::userDataFormatter($user); $stripeCustomer = $stripe->customers->create($userData); $user->stripe_id = $stripeCustomer->id; $user->save(); } } catch (\Stripe\Exception\InvalidRequestException $e) { // Customer doesn't exist, create a new customer $userData = self::userDataFormatter($user); $stripeCustomer = $stripe->customers->create($userData); $user->stripe_id = $stripeCustomer->id; $user->save(); } $couponCode = request()->input('coupon', null); $currency = Currency::where('id', $gateway->currency)->first()->code; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { $exception = __('Stripe product is not defined! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } if ($product->price_id == null) { $exception = __('Stripe product ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } $newDiscountedPrice = $plan->price; $newDiscountedPriceCents = $plan->price * 100; if ($couponCode != null) { $coupon = Coupon::where('code', $couponCode)->first(); if ($coupon) { $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); $newDiscountedPriceCents = (int) (((float) $newDiscountedPrice) * 100); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } } if ($gateway['automate_tax'] === 1) { try { Cashier::calculateTaxes(); $session = Session::create([ 'customer' => $user->stripe_id, 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => $currency, 'product_data' => [ 'name' => $plan->name, ], 'unit_amount' => $plan->price * 100, ], 'quantity' => 1, ]], 'mode' => 'payment', 'automatic_tax' => [ 'enabled' => true, ], 'metadata' => [ 'product_id' => $product->product_id, 'price_id' => $product->price_id, 'plan_id' => $plan->id, 'user_id' => $user->id, ], 'success_url' => url("webhooks/stripe/{$plan->id}/{$user->id}/success/prepaid?session_id={CHECKOUT_SESSION_ID}"), 'cancel_url' => url('webhooks/stripe/cancel/prepaid'), ]); DB::commit(); return redirect($session->url); } catch (Exception|InvalidRequestException $exception) { $dataCode = data_get($exception->getJsonBody(), 'error.code', null); if ($dataCode === 'customer_tax_location_invalid' && Auth::check()) { return redirect()->route('dashboard.user.settings.index')->with([ 'message' => __('Please update your address to continue.'), 'type' => 'error', ]); } throw new Exception('An error occurred while creating the session: ' . $exception->getMessage()); } } $paymentIntent = PaymentIntent::create([ 'amount' => $newDiscountedPriceCents, 'description' => 'AI Services', 'currency' => $currency, 'automatic_payment_methods' => [ 'enabled' => true, ], 'metadata' => [ 'product_id' => $product->product_id, 'price_id' => $product->price_id, 'plan_id' => $plan->id, ], ]); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'paymentIntent', 'product')); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $settings = Setting::getCache(); $key = self::getKey($gateway); Stripe::setApiKey($key); $user = auth()->user(); $stripe = new StripeClient($key); if ($referral !== null) { $stripe->customers->update( $user->stripe_id, [ 'metadata' => [ 'referral' => $referral, ], ] ); } $previousRequest = app('request')->create(url()->previous()); if ($request->has('payment_intent') && $request->has('payment_intent_client_secret') && $request->has('redirect_status')) { $payment_intent = $request->input('payment_intent'); $payment_intent_client_secret = $request->input('payment_intent_client_secret'); $redirect_status = $request->input('redirect_status'); if ($redirect_status != 'succeeded') { return back()->with(['message' => __("A problem occurred! $redirect_status"), 'type' => 'error']); } $intent = $stripe->paymentIntents->retrieve($payment_intent); if ($intent == null || $intent->client_secret != $payment_intent_client_secret || $intent->status != 'succeeded') { return back()->with(['message' => __('A problem occurred!'), 'type' => 'error']); } try { DB::beginTransaction(); $planId = $intent->metadata->plan_id; $productId = $intent->metadata->product_id; $priceId = $intent->metadata->price_id; $plan = Plan::where('id', $planId)->first(); if ($plan == null) { return back()->with(['message' => __('A problem occurred!'), 'type' => 'error']); } $total = $plan->price; if ($previousRequest->has('coupon')) { $coupon = Coupon::where('code', $previousRequest->input('coupon'))->first(); if ($coupon) { $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } $coupon->usersUsed()->attach(auth()->user()->id); } } $total += taxToVal($plan->price, $gateway->tax); $order = new UserOrder; $order->order_id = 'SPO-' . strtoupper(Str::random(13)); $order->plan_id = $plan->id; $order->user_id = $user->id; $order->type = 'prepaid'; $order->payment_type = self::$GATEWAY_CODE; $order->price = $total; $order->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $order->status = 'Success'; $order->country = $user->country ?? 'Unknown'; $order->tax_rate = $gateway->tax; $order->tax_value = taxToVal($plan->price, $gateway->tax); $order->save(); self::creditIncreaseSubscribePlan($user, $plan); // check if any other "AwaitingPayment" subscription exists if so cancel it $waiting_subscriptions = Subscriptions::where('paid_with', self::$GATEWAY_CODE)->where(['user_id' => $user->id, 'stripe_status' => 'AwaitingPayment'])->get(); foreach ($waiting_subscriptions as $waitingSubs) { dispatch(new CancelAwaitingPaymentSubscriptions($stripe, $waitingSubs)); } CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); } catch (Exception $th) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> prepaidCheckout(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } else { return back()->with(['message' => __('A problem occurred!'), 'type' => 'error']); } DB::commit(); return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success', ]); } // other functions public static function subscribeCancel($internalUser = null) { $key = self::getKey(); try { $user = $internalUser ?? Auth::user(); Stripe::setApiKey($key); $stripe = new StripeClient($key); $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { $plan = Plan::where('id', $activeSub->plan_id)->first(); try { $subscription = $stripe->subscriptions->retrieve($activeSub->stripe_id); if ($subscription != null) { $subscription->delete(); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . "::subscribeCancel()\n" . $ex->getMessage()); // return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } $activeSub->stripe_status = 'cancelled'; $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), $plan->name . ' ' . __('Plan')); if ($internalUser != null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return redirect()->route('dashboard.user.index')->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . "-> subscribeCancel():\n" . $th->getMessage()); return back()->with(['message' => $th->getMessage(), 'type' => 'error']); } } public static function cancelSubscribedPlan($subscription, $planId) { $key = self::getKey(); try { $user = Auth::user(); $user->subscription($planId)?->cancelNow(); $user->save(); $plan = Plan::where('id', $planId)->first(); self::creditDecreaseCancelPlan($user, $plan); $subscription->stripe_status = 'cancelled'; $subscription->save(); return true; } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . "-> cancelSubscribedPlan():\n" . $th->getMessage()); $plan = Plan::where('id', $planId)->first(); $subscription->stripe_status = 'cancelled'; $subscription->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } } public static function checkIfTrial() { $user = Auth::user(); $key = self::getKey(); $sub = getCurrentActiveSubscription($user->id); if ($sub != null) { try { return $user->subscription($sub->name)->onTrial(); } catch (Throwable $th) { return false; } } return false; } public static function getSubscriptionDaysLeft() { $sub = getCurrentActiveSubscription(Auth::id()); self::getKey(); try { $activeSub = $sub->asStripeSubscription(); if ($activeSub->status == 'active' || $activeSub->status == 'trialing') { return Carbon::now()->diffInDays(Carbon::createFromTimeStamp($activeSub->current_period_end)); } elseif ($sub->stripe_status == 'stripe_approved') { return Carbon::now()->diffInDays(Carbon::parse($sub->ends_at)); } else { return Carbon::now()->diffInDays(Carbon::parse($sub->trial_ends_at)); } } catch (Throwable $th) { if ($sub->stripe_status == 'stripe_approved') { return Carbon::now()->diffInDays(Carbon::parse($sub->ends_at)); } else { return Carbon::now()->diffInDays(Carbon::parse($sub->trial_ends_at)); } } } public static function getSubscriptionStatus($incomingUserId = null) { $incomingUserId == null ? $user = Auth::user() : $user = User::where('id', $incomingUserId)->first(); $settings = Setting::getCache(); $key = self::getKey(); $sub = getCurrentActiveSubscription($user->id); if ($sub != null) { try { if ($sub->asStripeSubscription()->status == 'active' || $sub->asStripeSubscription()->status == 'trialing' || $sub->stripe_status == 'stripe_approved') { return true; } else { $sub->stripe_status = 'cancelled'; $sub->ends_at = Carbon::now(); $sub->save(); return false; } } catch (Throwable $th) { if ($sub->stripe_status === 'stripe_approved') { return true; } else { $sub->stripe_status = 'cancelled'; $sub->ends_at = Carbon::now(); $sub->save(); return false; } } } return false; } public static function getKey(?Gateways $gateway = null): ?string { $gateway = $gateway ?? self::getGateway(); $isSandbox = $gateway->isSandbox(); $clientId = $isSandbox ? $gateway->getAttribute('sandbox_client_id') : $gateway->getAttribute('live_client_id'); $clientSecret = $isSandbox ? $gateway->getAttribute('sandbox_client_secret') : $gateway->getAttribute('live_client_secret'); Config::set('cashier.key', $clientId); Config::set('cashier.secret', $clientSecret); Config::set('cashier.currency', Helper::findCurrencyFromId($gateway?->currency)->code); return $clientSecret; } private static function updateUserData() { $key = self::getKey(); try { $history = OldGatewayProducts::where(['gateway_code' => self::$GATEWAY_CODE, 'status' => 'check'])->get(); if ($history != null) { $user = Auth::user(); $stripe = new StripeClient($key); foreach ($history as $record) { // check record current status from gateway $lookingFor = $record->old_price_id; // if active disable it if ($lookingFor !== 'undefined') { $stripe->prices->update($lookingFor, ['active' => false]); } // search subscriptions for record $subs = Subscriptions::where('paid_with', self::$GATEWAY_CODE) ->where('stripe_status', 'active') ->where('stripe_price', $lookingFor) ->get(); if ($subs != null) { foreach ($subs as $sub) { // cancel subscription order from gateway $user?->subscription($sub->name)?->cancelNow(); // cancel subscription from our database $sub->stripe_status = 'cancelled'; $sub->ends_at = Carbon::now(); $sub->save(); } } $record->status = 'checked'; $record->save(); } } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . "-> updateUserData():\n" . $ex->getMessage()); return ['result' => Str::before($ex->getMessage(), ':')]; } } private static function cancelAllSubscriptions() { $key = self::getKey(); try { $stripe = new StripeClient($key); $product = null; $user = Auth::user(); $allSubscriptions = getCurrentActiveSubscription($user->id); if ($allSubscriptions != null) { foreach ($allSubscriptions as $waitingSubs) { dispatch(new CancelAwaitingPaymentSubscriptions($stripe, $waitingSubs)); } // old code // foreach ($allSubscriptions as $subs) { // if ($subs->stripe_id != 'undefined' && $subs->stripe_id != null && $subs->user_id == $user->id) { // try{ // $subscription = $stripe->subscriptions->retrieve($subs->stripe_id); // if($subscription) { // $subscription->delete(); // } // }catch(\Exception $ex){ // Log::error("StripeController::cancelAllSubscriptions()\n" . $ex->getMessage()); // } // } // } } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . "-> cancelAllSubscriptions():\n" . $ex->getMessage()); } } public static function getSubscriptionRenewDate() { $user = Auth::user(); $key = self::getKey(); $end = null; $activeSub = getCurrentActiveSubscription($user->id); try { $activeSub->asStripeSubscription(); $end = $activeSub->current_period_end; } catch (Throwable $th) { $end = $activeSub->ends_at; } return Carbon::createFromTimeStamp($end)->format('F jS, Y'); } // webhook functions public static function verifyIncomingJson(Request $request) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); if (isset($gateway->webhook_secret)) { $secret = $gateway->webhook_secret; if (Str::startsWith($secret, 'whsec') == true) { $endpoint_secret = $secret; if ($request->hasHeader('stripe-signature') == true) { $sig_header = $request->header('stripe-signature'); } else { Log::error('(Webhooks) StripeController::verifyIncomingJson() -> Invalid header'); return null; } $payload = $request->getContent(); $event = null; try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); return json_encode($event); } catch (UnexpectedValueException $e) { // Invalid payload Log::error('(Webhooks) StripeController::verifyIncomingJson() -> Invalid payload : ' . $payload); return null; } catch (\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature Log::error('(Webhooks) StripeController::verifyIncomingJson() -> Invalid signature : ' . $payload); return null; } } } return null; } public static function handleWebhook(Request $request) { $verified = self::verifyIncomingJson($request); if ($verified != null) { // Retrieve the JSON payload $payload = $verified; // Fire the event with the payload event(new StripeWebhookEvent($payload)); return response()->json(['success' => true]); } else { // Incoming json is NOT verified abort(404); } } public static function createWebhook() { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $stripe = new StripeClient(self::getKey($gateway)); $webhooks = $stripe->webhookEndpoints->all(); if (count($webhooks['data']) > 0) { // There is/are webhook(s) defined. Remove existing. foreach ($webhooks['data'] as $hook) { $tmp = json_decode($stripe->webhookEndpoints->delete($hook->id, [])); if (isset($tmp->deleted)) { if ($tmp->deleted == false) { Log::error('Webhook ' . $hook->id . ' could not deleted.'); } } else { Log::error('Webhook ' . $hook->id . ' could not deleted.'); } } } // Create new webhook $url = url('/') . '/webhooks/stripe'; $events = [ 'invoice.paid', // A payment is made on a subscription. 'customer.subscription.deleted', // A subscription is cancelled. ]; $response = $stripe->webhookEndpoints->create([ 'url' => $url, 'enabled_events' => $events, ]); $gateway->webhook_id = $response->id; $gateway->webhook_secret = $response->secret; $gateway->save(); } catch (AuthenticationException $th) { Log::error('StripeController::createWebhook(): ' . $th->getMessage()); return back()->with(['message' => __('Stripe Authentication Error. Invalid API Key provided.'), 'type' => 'error']); } catch (InvalidArgumentException $th) { Log::error('StripeController::createWebhook(): ' . $th->getMessage()); return back()->with(['message' => __('You must provide Stripe API Key.'), 'type' => 'error']); } catch (Exception $th) { Log::error('StripeController::createWebhook(): ' . $th->getMessage()); return back()->with(['message' => 'Stripe Error : ' . $th->getMessage(), 'type' => 'error']); } } public static function gatewayDefinitionArray(): array { return [ 'code' => 'stripe', 'title' => 'Stripe', 'link' => 'https://stripe.com/', 'active' => 0, // if user activated this gateway - dynamically filled in main page 'available' => 1, // if gateway is available to use 'img' => '/assets/img/payments/stripe.svg', 'whiteLogo' => 0, // if gateway logo is white 'mode' => 1, // Option in settings - Automatically set according to the "Development" mode. "Development" ? sandbox : live (PAYPAL - 1) 'sandbox_client_id' => 1, // Option in settings 0-Hidden 1-Visible 'sandbox_client_secret' => 1, // Option in settings 'sandbox_app_id' => 0, // Option in settings 'live_client_id' => 1, // Option in settings 'live_client_secret' => 1, // Option in settings 'live_app_id' => 0, // Option in settings 'currency' => 1, // Option in settings 'currency_locale' => 0, // Option in settings 'base_url' => 1, // Option in settings 'sandbox_url' => 0, // Option in settings 'locale' => 0, // Option in settings 'validate_ssl' => 0, // Option in settings 'logger' => 0, // Option in settings 'notify_url' => 0, // Gateway notification url at our side 'webhook_secret' => 0, // Option in settings 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, 'automate_tax' => 1, ]; } private static function gatewayInstance(): Gateways { return Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->firstOrFail(); } public static function getGateway(): Gateways { if (self::$gateway === null) { self::setGateway(self::gatewayInstance()); } return self::$gateway; } public static function setGateway(Gateways $gateway): void { self::$gateway = $gateway; } } PayPalService.php000064400000150741152667044740010013 0ustar00get(); foreach ($plans as $plan) { self::saveProduct($plan); } // create webhooks after saving the products $tmp = self::createWebhook(); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): void { try { $paypal = self::getPaypalProvider(); DB::beginTransaction(); $plans = Plan::query()->where('active', 1)->get(); foreach ($plans as $plan) { $product = GatewayProducts::where([ 'plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE, ])->first(); if (! $product) { continue; } $productId = $product->getAttribute('product_id'); $filteredPlans = collect($paypal->listPlans()['plans'] ?? []) ->where('product_id', $productId) ->values(); if ($filteredPlans->isNotEmpty()) { $priceId = $filteredPlans->first()['id']; $product->price_id = $priceId; $product->save(); } } DB::commit(); } catch (Exception|Throwable $ex) { Log::error(self::$GATEWAY_CODE . '-> getPlansPriceIdsForMigration(): ' . $ex->getMessage()); DB::rollBack(); } } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); try { DB::beginTransaction(); $currency = Currency::where('id', $gateway->currency)->first()->code; $settings = Setting::getCache(); $user = Auth::user(); $oldProductId = null; $plan = Plan::where('id', $plan->id)->first(); // check product $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product != null) { // Create product in every situation. maybe user updated paypal credentials. if ($product->product_id !== null) { $oldProductId = $product->product_id; // Product has been created before } // ELSE Product has not been created before but record exists. Create new product and update record. } else { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_NAME; } $data = [ 'name' => $plan->name, 'description' => $plan->name, 'type' => 'SERVICE', 'category' => 'SOFTWARE', ]; $request_id = 'create-product-' . time(); $newProduct = $provider->createProduct($data, $request_id); $product->plan_name = $plan->name; $product->product_id = $newProduct['id']; $product->save(); if ($plan->price != 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'MONTH' : 'YEAR'; if ($plan->trial_days != 'undefined') { $trials = $plan->trial_days ?? 0; } else { $trials = 0; } $planData = self::createBillingPlanData($product->product_id, $plan->name, $trials, $currency, $interval, $plan->price, $gateway->tax); // This line is not in docs. but required in execution. Needed ~5 hours to fix. $request_id = 'create-plan-' . time(); $billingPlan = $provider->createPlan($planData, $request_id); // check price_id ( Billing plans id ), if there one make it old if ($product->price_id != null) { $history = new OldGatewayProducts; $history->plan_id = $plan->id; $history->plan_name = $plan->name; $history->gateway_code = self::$GATEWAY_CODE; $history->product_id = $product->product_id; $history->old_product_id = $oldProductId; $history->old_price_id = $product->price_id; // Deactivate old billing plan --> Moved to updateUserData() $history->new_price_id = $billingPlan['id']; $history->status = 'check'; $history->save(); $tmp = self::updateUserData(); } $product->price_id = $billingPlan['id']; } else { $product->price_id = 'Not Needed'; } $product->save(); DB::commit(); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> saveProduct(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribe($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); try { $currency = Currency::where('id', $gateway->currency)->first()->code; $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); $productId = $product->product_id; $settings = Setting::getCache(); $user = Auth::user(); $coupon = checkCouponInRequest(); $newDiscountedPrice = $plan->price; if ($product->product_id == null) { $exception = __('Paypal product is not defined! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } if ($product->price_id == null) { $exception = __('Paypal product ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } $taxRate = $gateway->tax; $taxValue = taxToVal($newDiscountedPrice, $taxRate); if ($plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { // if there is a coupon discount then change the biling plan if ($newDiscountedPrice != $plan->price) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'MONTH' : 'YEAR'; if ($plan->trial_days != 'undefined') { $trials = $plan->trial_days ?? 0; } else { $trials = 0; } $planData = self::createBillingPlanData($product->product_id, $plan->name, $trials, $currency, $interval, $plan->price, $gateway->tax, $newDiscountedPrice); // This line is not in docs. but required in execution. Needed ~5 hours to fix. $request_id = 'create-plan-' . time(); $billingPlan = $provider->createPlan($planData, $request_id); $billingPlanId = $billingPlan['id']; } else { $billingPlanId = $product->price_id; } } else { // if lifetime plans $billingPlanId = null; } return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'billingPlanId', 'productId', 'gateway')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $couponID = $request->input('couponID', null); $paypalSubscriptionID = $request->input('paypalSubscriptionID', null); $billingPlanId = $request->input('billingPlanId', null); $productId = $request->input('productId', null); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); $user = Auth::user(); try { DB::beginTransaction(); $plan = Plan::where('id', $planID)->first(); $product = GatewayProducts::where(['plan_id' => $plan->id, 'product_id' => $productId, 'gateway_code' => self::$GATEWAY_CODE])->first(); // check if $product->price_id != to $billingPlanId, if not thats means its custom plan then save it in custom plans table. if ($product->price_id != $billingPlanId) { $newcustom = new CustomBilingPlans; $newcustom->gateway = self::$GATEWAY_CODE; $newcustom->plan_id = $planID; $newcustom->main_plan_price_id = $product->price_id; $newcustom->custom_plan_price_id = $billingPlanId; $newcustom->save(); } $total = $plan->price; $taxValue = taxToVal($plan->price, $gateway->tax); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $coupon->usersUsed()->attach(auth()->user()->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $total += $taxValue; // new subscription $subscription = new Subscriptions; if ($billingPlanId == 'lifetime' && $paypalSubscriptionID == 'lifetime') { $subscription->stripe_id = 'PLS-' . strtoupper(Str::random(13)); $subscription->stripe_price = $product->price_id; $subscription->ends_at = $plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value ? \Carbon\Carbon::now()->addMonths(1) : \Carbon\Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->stripe_status = 'paypal_approved'; $subscription->trial_ends_at = null; } else { $subscription->stripe_id = $paypalSubscriptionID; $subscription->stripe_price = $billingPlanId; $subscription->stripe_status = 'active'; $subscription->ends_at = $plan->trial_days != 0 ? \Carbon\Carbon::now()->addDays($plan->trial_days) : \Carbon\Carbon::now()->addDays(30); $subscription->trial_ends_at = $plan->trial_days != 0 ? \Carbon\Carbon::now()->addDays($plan->trial_days) : null; } $subscription->user_id = $user->id; $subscription->name = $planID; $subscription->quantity = 1; $subscription->plan_id = $planID; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->tax_rate = $gateway->tax; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $total; $subscription->save(); // new subscription item // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $subscription->stripe_id; // $subscriptionItem->stripe_product = $productId; // $subscriptionItem->stripe_price = $subscription->stripe_price; // $subscriptionItem->quantity = 1; // $subscriptionItem->save(); // new order $payment = new UserOrder; $payment->order_id = $subscription->stripe_id; $payment->plan_id = $plan->id; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $total; $payment->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $gateway->tax; $payment->tax_value = $taxValue; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Subscribed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); DB::commit(); if (class_exists('App\Extensions\Affilate\System\Events\AffiliateEvent')) { event(new \App\Extensions\Affilate\System\Events\AffiliateEvent($total, $gateway->currency)); } return ['result' => 'OK']; } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return ['result' => $ex->getMessage()]; } } public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); try { $newDiscountedPrice = $plan->price; $currency = Currency::where('id', $gateway->currency)->first()->code; $coupon = checkCouponInRequest(); $productId = self::getPaypalProductId($plan); $taxRate = $gateway->tax; $taxValue = taxToVal($newDiscountedPrice, $taxRate); if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } if ($taxValue > 0) { $newDiscountedPrice += $taxValue; } if ($productId == null) { $exception = __('Paypal product is not defined! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'productId', 'taxValue', 'taxRate', 'gateway', 'currency')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $productId = $request->input('productId', null); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); $user = Auth::user(); try { DB::beginTransaction(); $plan = Plan::where('id', $planID)->first(); $total = $plan->price; $taxValue = taxToVal($plan->price, $gateway->tax); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $coupon->usersUsed()->attach(auth()->user()->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $total += $taxValue; // new order $payment = new UserOrder; $payment->order_id = $orderID; $payment->plan_id = $plan->id; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $total; $payment->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $gateway->tax; $payment->tax_value = $taxValue; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); DB::commit(); return ['result' => 'OK']; } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return ['result' => $ex->getMessage()]; } } // other functions public static function cancelSubscribedPlan($subscription, $planId) { $user = Auth::user(); $provider = self::getPaypalProvider(); if ($subscription != null) { $plan = Plan::where('id', $planId)->first(); if ($subscription->stripe_status == 'paypal_approved') { $subscription->stripe_status = 'cancelled'; $subscription->ends_at = Carbon::now(); $subscription->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } else { $response = $provider->cancelSubscription($subscription->stripe_id, 'Plan deleted by admin.'); if ($response == '') { $subscription->stripe_status = 'cancelled'; $subscription->ends_at = \Carbon\Carbon::now(); $subscription->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } } } return false; } public static function getSubscriptionRenewDate() { $provider = self::getPaypalProvider(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($activeSub->stripe_status == 'paypal_approved') { return \Carbon\Carbon::createFromTimeStamp($activeSub->ends_at)->format('F jS, Y'); } else { $subscription = $provider->showSubscriptionDetails($activeSub->stripe_id); if (isset($subscription['error'])) { Log::error("PayPalService::getSubscriptionRenewDate() :\n" . json_encode($subscription)); return back()->with(['message' => 'PayPal Gateway : ' . $subscription['error']['message'], 'type' => 'error']); } if ($subscription['billing_info']['next_billing_time']) { return \Carbon\Carbon::parse($subscription['billing_info']['next_billing_time'])->format('F jS, Y'); } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); return \Carbon\Carbon::now()->format('F jS, Y'); } } } return null; } public static function checkIfTrial() { $provider = self::getPaypalProvider(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($activeSub->stripe_status == 'paypal_approved') { return false; } else { $subscription = $provider->showSubscriptionDetails($activeSub->stripe_id); if (isset($subscription['error'])) { Log::error("PayPalService::getSubscriptionStatus() :\n" . json_encode($subscription)); return back()->with(['message' => 'PayPal Gateway : ' . $subscription['error']['message'], 'type' => 'error']); } if (isset($subscription['billing_info']['cycle_executions'][0]['tenure_type'])) { if ($subscription['billing_info']['cycle_executions'][0]['tenure_type'] == 'TRIAL') { return true; } } } } return false; } public static function subscribeCancel($internalUser = null) { $user = $internalUser ?? Auth::user(); $provider = self::getPaypalProvider(); $userId = $user->id; // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { $plan = Plan::where('id', $activeSub->plan_id)->first(); if ($activeSub->stripe_status == 'paypal_approved') { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } else { $response = $provider->cancelSubscription($activeSub->stripe_id, 'Not satisfied with the service'); if ($response == '') { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), 'Subscription plan'); if ($internalUser != null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } else { return back()->with(['message' => __('Your subscription could not cancelled.'), 'type' => 'error']); } } } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function getSubscriptionDaysLeft() { $provider = self::getPaypalProvider(); $userId = Auth::user()->id; // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($activeSub->stripe_status == 'paypal_approved') { return Carbon::now()->diffInDays(Carbon::parse($activeSub->ends_at)); } else { $subscription = $provider->showSubscriptionDetails($activeSub->stripe_id); if (! isset($subscription['error'])) { // if user is in trial if (isset($subscription['billing_info']['cycle_executions'][0]['tenure_type'])) { if ($subscription['billing_info']['cycle_executions'][0]['tenure_type'] == 'TRIAL') { return $subscription['billing_info']['cycle_executions'][0]['cycles_remaining']; } else { if (isset($subscription['billing_info']['next_billing_time'])) { return \Carbon\Carbon::now()->diffInDays(\Carbon\Carbon::parse($subscription['billing_info']['next_billing_time'])); } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); return \Carbon\Carbon::now()->format('F jS, Y'); } } } } else { Log::error("PayPalService::getSubscriptionDaysLeft() :\n" . json_encode($subscription)); } } } return null; } public static function getSubscriptionStatus() { $provider = self::getPaypalProvider(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { // when lifetime if ($activeSub->stripe_status == 'paypal_approved') { // TODO: we can renew from here or from command return true; } else { $subscription = $provider->showSubscriptionDetails($activeSub->stripe_id); if (isset($subscription['error'])) { Log::error("PayPalService::getSubscriptionStatus() :\n" . json_encode($subscription)); return back()->with(['message' => 'PayPal Gateway : ' . $subscription['error']['message'], 'type' => 'error']); } if ($subscription['status'] == 'ACTIVE') { return true; } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); return false; } } } return null; } public static function createPayPalOrder(Request $request) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); try { $couponID = $request->input('couponID', null); $planID = $request->input('plan_id', null); $plan = Plan::where('id', $planID)->first(); $newDiscountedPrice = $plan->price; $currency = Currency::where('id', $gateway->currency)->first()->code; $taxRate = $gateway->tax; $taxValue = taxToVal($newDiscountedPrice, $taxRate); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } } if ($taxValue > 0) { $newDiscountedPrice += $taxValue; } $data = [ 'intent' => 'CAPTURE', 'purchase_units' => [ [ 'amount' => [ 'currency_code' => $currency, 'value' => strval($newDiscountedPrice), ], ], ], ]; $order = $provider->createOrder($data); return response()->json(['id' => $order['id']]); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> createPayPalOrder(): ' . $ex->getMessage()); return response()->json([ 'message' => $ex->getMessage(), ], 500); } $plan = Plan::where('id', $request->plan_id)->first(); $newPrice = $plan->price; $previousRequest = app('request')->create(url()->previous()); if ($previousRequest->has('coupon')) { $coupon = Coupon::where('code', $previousRequest->input('coupon'))->first(); if ($coupon) { $newPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); } } $user = Auth::user(); $settings = Setting::getCache(); $provider = self::getPaypalProvider(); $data = [ 'intent' => 'CAPTURE', 'purchase_units' => [ [ 'amount' => [ 'currency_code' => $request->currency, 'value' => strval($newPrice), ], ], ], ]; $order = $provider->createOrder($data); $orderId = $order['id']; $payment = new UserOrder; $payment->order_id = $orderId; $payment->plan_id = $plan->id; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = 'PayPal'; $payment->price = $newPrice; $payment->affiliate_earnings = ($newPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Waiting'; $payment->country = $user->country ?? 'Unknown'; $payment->save(); return $order; } // webhook functions public static function createWebhook() { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $provider = self::getPaypalProvider($gateway); try { $user = Auth::user(); $webhooks = $provider->listWebHooks(); if (count($webhooks['webhooks']) > 0) { // There is/are webhook(s) defined. Remove existing. foreach ($webhooks['webhooks'] as $key => $hook) { $provider->deleteWebHook($hook['id']); } } // Create new webhook $url = url('/') . '/webhooks/paypal'; $events = [ 'PAYMENT.SALE.COMPLETED', // A payment is made on a subscription. 'BILLING.SUBSCRIPTION.CANCELLED', // A subscription is cancelled. ]; // 'BILLING.SUBSCRIPTION.EXPIRED', # A subscription expires. // 'BILLING.SUBSCRIPTION.SUSPENDED' # A subscription is suspended. $response = $provider->createWebHook($url, $events); $gateway->webhook_id = $response['id']; $gateway->save(); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> createWebhook(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } private static function verifyIncomingJson(Request $request) { try { $gateway = Gateways::where('code', 'paypal')->first(); if ($gateway->mode == 'sandbox') { // Paypal does not support verification on sandbox mode return true; } if ($request->hasHeader('PAYPAL-AUTH-ALGO') == true) { $auth_algo = $request->header('PAYPAL-AUTH-ALGO'); } else { return false; } if ($request->hasHeader('PAYPAL-CERT-URL') == true) { $cert_url = $request->header('PAYPAL-CERT-URL'); } else { return false; } if ($request->hasHeader('PAYPAL-TRANSMISSION-ID') == true) { $transmission_id = $request->header('PAYPAL-TRANSMISSION-ID'); } else { return false; } if ($request->hasHeader('PAYPAL-TRANSMISSION-SIG') == true) { $transmission_sig = $request->header('PAYPAL-TRANSMISSION-SIG'); } else { return false; } if ($request->hasHeader('PAYPAL-TRANSMISSION-TIME') == true) { $transmission_time = $request->header('PAYPAL-TRANSMISSION-TIME'); } else { return false; } $webhook_event = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); if ($webhook_event == null) { return false; } $webhook_id = $gateway->webhook_id; if ($webhook_id == null) { return false; } $data = [ 'auth_algo' => $auth_algo, 'cert_url' => $cert_url, 'transmission_id' => $transmission_id, 'transmission_sig' => $transmission_sig, 'transmission_time' => $transmission_time, 'webhook_id' => $webhook_id, 'webhook_event' => $webhook_event, ]; $provider = self::getPaypalProvider(); $response = $provider->verifyWebHook($data); if (is_array($response)) { if ($response['verification_status'] === 'SUCCESS') { return true; } } elseif (is_object($response)) { if ($response?->verification_status === 'SUCCESS') { return true; } } } catch (Exception $th) { Log::error('(Webhooks) PayPalService::verifyIncomingJson(): ' . $th->getMessage()); } return false; } public static function handleWebhook(Request $request) { $verified = self::verifyIncomingJson($request); if ($verified == true) { // Retrieve the JSON payload $payload = $request->getContent(); // Fire the event with the payload event(new PayPalWebhookEvent($payload)); return response()->json(['success' => true]); } else { // Incoming json is NOT verified abort(404); } } public static function simulateWebhookEvent() { $url = url('/') . '/webhooks/paypal'; $testJson = [ 'event_type' => 'PAYMENT.SALE.COMPLETED', 'url' => $url, 'resource_version' => '1.0', ]; $provider = self::getPaypalProvider(); $filters = [ 'start_date' => Carbon::now()->subDays(7)->toIso8601String(), 'end_date' => Carbon::now()->addDays(2)->toIso8601String(), ]; return $provider->listTransactions($filters); } // helper private functions public static function getPaypalProvider($gateway = null) { $gateway = $gateway ?? Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $settings = Setting::getCache(); $config = null; if ($gateway->mode == 'sandbox') { $config = [ 'mode' => 'sandbox', 'sandbox' => [ 'client_id' => $gateway->sandbox_client_id, 'client_secret' => $gateway->sandbox_client_secret, 'app_id' => $gateway->live_app_id, ], 'payment_action' => 'Sale', 'currency' => $currency, 'notify_url' => $settings->site_url . '/paypal/notify', 'locale' => $gateway->currency_locale, 'validate_ssl' => false, ]; } else { $config = [ 'mode' => 'live', 'live' => [ 'client_id' => $gateway->live_client_id, 'client_secret' => $gateway->live_client_secret, 'app_id' => $gateway->live_app_id, ], 'payment_action' => 'Sale', 'currency' => $currency, 'notify_url' => $settings->site_url . '/paypal/notify', 'locale' => $gateway->currency_locale, 'validate_ssl' => true, ]; } $provider = new PayPalClient($config); $provider->getAccessToken(); return $provider; } private static function getPaypalProductId($plan) { if ($plan != null) { $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product != null) { return $product->product_id; } else { return null; } } return null; } private static function getPaypalPriceId($plan) { if ($plan != null) { $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product != null) { return $product->price_id; } else { return null; } } return null; } private static function updateUserData() { // Since price id (billing plan) is changed, we must update user data, i.e cancel current subscriptions. $history = OldGatewayProducts::where([ 'gateway_code' => self::$GATEWAY_CODE, 'status' => 'check', ])->get(); if ($history) { $provider = self::getPaypalProvider(); foreach ($history as $record) { // check record current status from gateway $lookingFor = $record->old_price_id; // billingPlan id in paypal // if active disable it $oldBillingPlan = $provider->deactivatePlan($lookingFor); if ($oldBillingPlan == '') { // deactivated billing plan from gateway } else { Log::error(self::$GATEWAY_CODE . "-> updateUserData(): \n" . json_encode($oldBillingPlan)); } // search subscriptions for record $subs = Subscriptions::where([ 'paid_with' => self::$GATEWAY_CODE, 'stripe_status' => 'active', 'stripe_price' => $lookingFor, ])->get(); if ($subs != null) { foreach ($subs as $sub) { // if found get order id $orderId = $sub->stripe_id; // cancel subscription order from gateway $response = $provider->cancelSubscription($orderId, 'New plan created by admin.'); // cancel subscription from our database $sub->stripe_status = 'cancelled'; $sub->ends_at = \Carbon\Carbon::now(); $sub->save(); } } $record->status = 'checked'; $record->save(); } } } private static function createBillingPlanData($productId, $productName, $trials, $currency, $interval, $price, $tax, $discountedPrice = null) { if ($trials == 0) { if ($discountedPrice != null) { $planData = [ 'product_id' => $productId, 'name' => $productName, 'description' => 'Billing Plan of ' . $productName, 'status' => 'ACTIVE', 'billing_cycles' => [ [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'TRIAL', 'sequence' => 1, 'total_cycles' => 1, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($discountedPrice), 'currency_code' => $currency, ], ], ], [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'REGULAR', 'sequence' => $discountedPrice == null ? 1 : 2, 'total_cycles' => 0, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($price), 'currency_code' => $currency, ], ], ], ], 'payment_preferences' => [ 'auto_bill_outstanding' => true, 'setup_fee' => [ 'value' => '0', 'currency_code' => $currency, ], 'setup_fee_failure_action' => 'CANCEL', 'payment_failure_threshold' => 3, ], ]; } else { $planData = [ 'product_id' => $productId, 'name' => $productName, 'description' => 'Billing Plan of ' . $productName, 'status' => 'ACTIVE', 'billing_cycles' => [ [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'REGULAR', 'sequence' => $discountedPrice == null ? 1 : 2, 'total_cycles' => 0, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($price), 'currency_code' => $currency, ], ], ], ], 'payment_preferences' => [ 'auto_bill_outstanding' => true, 'setup_fee' => [ 'value' => '0', 'currency_code' => $currency, ], 'setup_fee_failure_action' => 'CANCEL', 'payment_failure_threshold' => 3, ], ]; } } else { if ($discountedPrice != null) { $planData = [ 'product_id' => $productId, 'name' => $productName, 'description' => 'Billing Plan of ' . $productName, 'status' => 'ACTIVE', 'billing_cycles' => [ [ 'frequency' => [ 'interval_unit' => 'DAY', 'interval_count' => 1, ], 'tenure_type' => 'TRIAL', 'sequence' => 1, 'total_cycles' => $trials, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => 0, 'currency_code' => $currency, ], ], ], [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'TRIAL', 'sequence' => 2, 'total_cycles' => 1, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($discountedPrice), 'currency_code' => $currency, ], ], ], [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'REGULAR', 'sequence' => $discountedPrice == null ? 2 : 3, 'total_cycles' => 0, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($price), 'currency_code' => $currency, ], ], ], ], 'payment_preferences' => [ 'auto_bill_outstanding' => true, 'setup_fee' => [ 'value' => '0', 'currency_code' => $currency, ], 'setup_fee_failure_action' => 'CANCEL', 'payment_failure_threshold' => 3, ], ]; } else { $planData = [ 'product_id' => $productId, 'name' => $productName, 'description' => 'Billing Plan of ' . $productName, 'status' => 'ACTIVE', 'billing_cycles' => [ [ 'frequency' => [ 'interval_unit' => 'DAY', 'interval_count' => 1, ], 'tenure_type' => 'TRIAL', 'sequence' => 1, 'total_cycles' => $trials, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => 0, 'currency_code' => $currency, ], ], ], [ 'frequency' => [ 'interval_unit' => $interval, 'interval_count' => 1, ], 'tenure_type' => 'REGULAR', 'sequence' => $discountedPrice == null ? 2 : 3, 'total_cycles' => 0, 'pricing_scheme' => [ 'fixed_price' => [ 'value' => strval($price), 'currency_code' => $currency, ], ], ], ], 'payment_preferences' => [ 'auto_bill_outstanding' => true, 'setup_fee' => [ 'value' => '0', 'currency_code' => $currency, ], 'setup_fee_failure_action' => 'CANCEL', 'payment_failure_threshold' => 3, ], ]; } } if ($tax > 0) { $planData['taxes'] = [ 'percentage' => $tax, 'inclusive' => false, ]; } return $planData; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'paypal', 'title' => 'PayPal', 'link' => 'https://www.paypal.com/', 'active' => 0, 'available' => 1, 'img' => '/assets/img/payments/paypal.svg', 'whiteLogo' => 0, 'mode' => 1, 'sandbox_client_id' => 1, 'sandbox_client_secret' => 1, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 1, 'live_app_id' => 1, 'currency' => 1, 'currency_locale' => 1, 'notify_url' => 0, 'base_url' => 0, 'sandbox_url' => 0, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } FreeService.php000064400000034166152667044740007510 0ustar00 $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($productData == null) { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->plan_name = $plan->name; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_NAME; $product->product_id = 'FPP-' . strtoupper(Str::random(13)); $product->price_id = 'Not Needed'; $product->save(); } else { $productData->plan_name = $plan->name; $productData->save(); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveProduct(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function subscribe($plan) { if ($plan->price != 0) { abort(404); } try { $user = auth()->user(); $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); } $order_id = 'FPS-' . strtoupper(Str::random(13)); return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'order_id')); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $user = auth()->user(); $plan = Plan::find($planID) ?? abort(404); $total = $plan->price; switch ($plan->frequency) { case FrequencyEnum::MONTHLY->value: $previousPeriod = now()->subMonth(); break; case FrequencyEnum::YEARLY->value: $previousPeriod = now()->subYear(); break; case FrequencyEnum::LIFETIME_MONTHLY->value: $previousPeriod = now()->subMonth(); break; case FrequencyEnum::LIFETIME_YEARLY->value: $previousPeriod = now()->subYear(); break; default: $previousPeriod = now()->subMonth(); break; } $existingOrder = UserOrder::where('user_id', $user->id) ->where('type', 'subscription') ->where('status', 'Approved') ->where('created_at', '>=', $previousPeriod) ->first(); if ($existingOrder) { return back()->with(['message' => __('You cannot subscribe to another free plan during the current period.'), 'type' => 'error']); } try { DB::beginTransaction(); $subscription = new Subscriptions; $subscription->user_id = $user->id; $subscription->name = $plan->id; $subscription->stripe_id = $orderID; $subscription->stripe_status = 'free_approved'; $subscription->stripe_price = 'Not Needed'; $subscription->quantity = 1; $subscription->trial_ends_at = null; switch ($plan->frequency) { case FrequencyEnum::MONTHLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addMonths(1); $subscription->auto_renewal = 1; break; case FrequencyEnum::YEARLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addYears(1); $subscription->auto_renewal = 1; break; case FrequencyEnum::LIFETIME_MONTHLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addMonths(1); // ends each month but auto renewing without payment reqs $subscription->auto_renewal = 1; break; case FrequencyEnum::LIFETIME_YEARLY->value: $subscription->ends_at = \Carbon\Carbon::now()->addYears(1); // ends each year but auto renewing without payment reqs $subscription->auto_renewal = 1; break; default: $subscription->ends_at = \Carbon\Carbon::now()->addDays(30); $subscription->auto_renewal = 1; break; } $subscription->tax_rate = 0; $subscription->tax_value = 0; $subscription->coupon = null; $subscription->total_amount = $total; $subscription->plan_id = $plan->id; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->save(); $order = new UserOrder; $order->order_id = $orderID; $order->plan_id = $plan->id; $order->user_id = $user->id; $order->payment_type = self::$GATEWAY_CODE; $order->price = $total; $order->affiliate_earnings = 0; $order->status = 'Approved'; $order->type = 'subscription'; $order->country = $user->country ?? 'Unknown'; $order->tax_rate = 0; $order->tax_value = 0; $order->save(); self::creditIncreaseSubscribePlan($user, $plan); // sent mail if required here later CreateActivity::for($order->user, __('Purchased'), $order->plan->name . ' ' . __('Plan') . ' ' . __('For free')); EmailPaymentConfirmation::create($user, $plan)->send(); } catch (Exception $th) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } DB::commit(); return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success', ]); } public static function prepaid($plan) { if ($plan->price != 0) { abort(404); } try { $user = auth()->user(); $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); } $existingPrepaidOrder = UserOrder::where('user_id', $user->id) ->where('type', 'prepaid') ->where('status', 'Approved') ->first(); if ($existingPrepaidOrder) { $order_id = $existingPrepaidOrder->order_id; } else { $order_id = 'FPO-' . strtoupper(Str::random(13)); } return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'order_id', 'existingPrepaidOrder')); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $user = auth()->user(); $plan = Plan::find($planID) ?? abort(404); $total = $plan->price; $existingPrepaidOrder = UserOrder::where('user_id', $user->id) ->where('order_id', $orderID) ->where('type', 'prepaid') ->where('status', 'Approved') ->first(); if ($existingPrepaidOrder) { return back()->with(['message' => __('This pack alredy purchased'), 'type' => 'error']); } try { DB::beginTransaction(); $order = new UserOrder; $order->order_id = $orderID; $order->plan_id = $plan->id; $order->user_id = $user->id; $order->type = 'prepaid'; $order->payment_type = self::$GATEWAY_CODE; $order->price = $plan->price; $order->affiliate_earnings = 0; $order->status = 'Approved'; $order->country = $user->country ?? 'Unknown'; $order->tax_rate = 0; $order->tax_value = 0; $order->save(); self::creditIncreaseSubscribePlan($user, $plan); // sent mail if required here later CreateActivity::for($order->user, __('Purchased'), $order->plan->name . ' ' . __('Plan') . ' ' . __('For free')); EmailPaymentConfirmation::create($user, $plan)->send(); } catch (Exception $th) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } DB::commit(); return redirect()->route('dashboard.user.payment.succesful')->with([ 'message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success', ]); } public static function getSubscriptionStatus($incomingUserId = null) { if ($incomingUserId != null) { $user = User::where('id', $incomingUserId)->first(); } else { $user = Auth::user(); } $sub = getCurrentActiveSubscription($user->id); if ($sub != null) { return true; } return false; } public static function getSubscriptionDaysLeft() { $user = Auth::user(); $sub = getCurrentActiveSubscription($user->id); if ($sub) { return \Carbon\Carbon::now()->diffInDays($sub->ends_at); } else { Log::error('getSubscriptionDaysLeft()'); return 0; } } public static function subscribeCancel($internalUser = null) { $user = $internalUser ?? Auth::user(); $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { $plan = Plan::where('id', $activeSub->plan_id)->first(); self::creditDecreaseCancelPlan($user, $plan); $activeSub->stripe_status = 'free_canceled'; $activeSub->save(); CreateActivity::for($user, 'cancelled', $plan->name); if ($internalUser != null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return redirect()->route('dashboard.user.index')->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function checkIfTrial() { // there is no trail in free return false; } public static function getSubscriptionRenewDate() { $user = Auth::user(); $activeSub = getCurrentActiveSubscription($user->id); return \Carbon\Carbon::parse($activeSub->ends_at)->format('F jS, Y'); } public static function cancelSubscribedPlan($subscription, $planId) { try { $order = UserOrder::where('order_id', $subscription->stripe_id)->first(); /** * @var Plan $plan */ $plan = $order->plan; /** * @var User $user */ $user = $order->user; self::creditDecreaseCancelPlan($user, $plan); $subscription->stripe_status = 'free_canceled'; $subscription->save(); // sent mail if required here later CreateActivity::for($order->user, __('Subscription canceled due to plan deletion.'), $order->plan->name . ' ' . __('Plan')); return true; } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . ' cancelSubscribedPlan(): ' . $th->getMessage() . "\n------------------------\n"); return false; } } public static function handleWebhook(Request $request) { return response()->json(['success' => true]); } } PaystackService.php000064400000127464152667044740010412 0ustar00get(); foreach ($plans as $plan) { self::saveProduct($plan); } } catch (Exception $ex) { Log::error("paystack::saveAllProducts()\n" . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): void { try { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first(); if (! $gateway) { throw new Exception('Active Paystack gateway not found.'); } $key = self::getKey($gateway); $paystackPlansResponse = self::curl_req(self::$plan_endpoint . '?perPage=100', $key, [], 'GET'); if (! isset($paystackPlansResponse['data'])) { throw new RuntimeException('Could not fetch plans from Paystack.'); } $paystackPlans = collect($paystackPlansResponse['data']); DB::beginTransaction(); $plans = Plan::query()->where('active', 1)->get(); foreach ($plans as $plan) { $product = GatewayProducts::where([ 'plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE, ])->first(); if (! $product) { continue; } $productId = $product->getAttribute('product_id'); $matchedPlan = $paystackPlans->firstWhere('description', $productId); if ($matchedPlan) { $priceId = $matchedPlan['plan_code']; if ($product->price_id !== $priceId) { $product->price_id = $priceId; $product->save(); } } } DB::commit(); } catch (Exception|Throwable $ex) { Log::error(self::$GATEWAY_CODE . '-> getPlansPriceIdsForMigration(): ' . $ex->getMessage()); DB::rollBack(); } } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { // 1 begain db transaction DB::beginTransaction(); $currency = Currency::where('id', $gateway->currency)->first()->code; $taxValue = taxToVal($plan->price, $gateway->tax); $total = $plan->price + $taxValue; $price = (int) (((float) $total) * 100); // Must be in cents level for paystack $key = self::getKey($gateway); $oldProductId = null; // 2 check if product exists. No->create, Yes->create->assign old to $oldProductId. (Create product in every situation. maybe user updated stripe credentials.) $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); $data = [ 'name' => $plan->name, 'description' => $plan->name, 'price' => $price == 0 ? 1000 : $price, 'currency' => $currency, ]; // Create product in every situation. maybe user updated paystack credentials. $newProduct = self::curl_req(self::$product_endpoint, $key, $data); if ($product != null) { if ($product->product_id != null) { $oldProductId = $product->product_id; // Product has been created before } // ELSE Product has not been created before but record exists. Create new product and update record. } else { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_NAME; } $product->product_id = $newProduct['data']['product_code']; $product->plan_name = $plan->name; $product->save(); // if not lifetime or free or onetime then create priceID if ($plan->price != 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? FrequencyEnum::MONTHLY->value : 'annually'; $billingPlan = self::curl_req(self::$plan_endpoint, $key, [ 'name' => $plan->name, 'interval' => $interval, 'amount' => $price, 'description' => $product->product_id, 'currency' => $currency, ]); if ($product->price_id != null) { $history = new OldGatewayProducts; $history->plan_id = $plan->id; $history->plan_name = $plan->name; $history->gateway_code = self::$GATEWAY_CODE; $history->product_id = $product->product_id; $history->old_product_id = $oldProductId; $history->old_price_id = $product->price_id; $history->new_price_id = $billingPlan['data']['plan_code']; $history->status = 'check'; $history->save(); $tmp = self::updateUserData(); } $product->price_id = $billingPlan['data']['plan_code']; } else { $product->price_id = 'Not Needed'; } $product->save(); DB::commit(); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . "-> saveProduct():\n" . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } // tested public static function subscribe($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { DB::beginTransaction(); $planId = $plan->id; $settings = Setting::getCache(); $exception = null; $orderId = Str::random(12); $currency = Currency::where('id', $gateway->currency)->first()->code; $key = self::getKey($gateway); $user = auth()->user(); $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $productId = self::getPaystackProductId($plan->id); $billingPlanId = self::getPaystackPriceId($plan->id); if ($productId == null) { $exception = __('Product ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } if ($billingPlanId == null) { $exception = __('Product Price ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } $newDiscountedPrice = $plan->price + $taxValue; // total with tax if ($coupon && $plan->price != 0) { $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } if ($plan->price != 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? FrequencyEnum::MONTHLY->value : 'annually'; $billingPlan = self::curl_req(self::$plan_endpoint, $key, [ 'name' => 'discount_item_' . time(), 'interval' => $interval, 'amount' => (int) (((float) $newDiscountedPrice) * 100), 'description' => 'coupon_' . $coupon->code . '_user_' . $user->id . '_plan_' . $plan->id, 'currency' => $currency, ]); $billingPlanId = $billingPlan['data']['plan_code']; } // remove tax when coupon is applied because its included in the plan price. $newDiscountedPrice -= $taxValue; } $payment = new UserOrder; $payment->order_id = $orderId; $payment->plan_id = $plan->id; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $newDiscountedPrice; $payment->affiliate_earnings = ($newDiscountedPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Waiting'; $payment->country = $user->country ?? 'Unknown'; $payment->save(); DB::commit(); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'taxRate', 'taxValue', 'newDiscountedPrice', 'billingPlanId', 'exception', 'orderId', 'productId', 'gateway', 'planId')); } // tested public static function subscribeCheckout(Request $request, $referral = null) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $planId = $request->input('planID', null); $couponID = $request->input('couponID', null); $orderId = $request->input('orderID', null); $billingPlanId = $request->input('billingPlanId', null); $productId = $request->input('productID', null); DB::beginTransaction(); $payment_response = json_decode($request->response, true); $payment_response_status = $payment_response['status']; $payment_response_message = $payment_response['message']; $payment_response_reference = $payment_response['reference']; $plan = Plan::where('id', $planId)->first(); $payment = UserOrder::where('order_id', $orderId)->first(); $user = auth()->user(); $product = GatewayProducts::where(['plan_id' => $plan->id, 'product_id' => $productId, 'gateway_code' => self::$GATEWAY_CODE])->first(); // check if $product->price_id != to $billingPlanId, if not thats means its custom plan then save it in custom plans table. if ($product->price_id != $billingPlanId) { $newcustom = new CustomBilingPlans; $newcustom->gateway = self::$GATEWAY_CODE; $newcustom->plan_id = $planId; $newcustom->main_plan_price_id = $product->price_id; $newcustom->custom_plan_price_id = $billingPlanId; $newcustom->save(); } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } // verify transaction with paystack if it was successful then continue $reqs = self::curl_req_get(self::$transaction_verify_endpoint . $payment_response_reference, $key); if ($reqs['status'] == false) { // if something went wrong with the request abort(404); } // failed or success if ($reqs['data']['status'] != 'success') { // if the transaction was not successful abort(400, $reqs['data']['gateway_response']); } // log the transaction data to database $info = new PaystackPaymentInfo; $info->user_id = $user->id; $info->email = $user->email; $info->reference = $payment_response['reference'] ?? ''; $info->trans = $payment_response['trans'] ?? ''; $info->status = $payment_response['status'] ?? ''; $info->message = $payment_response['message'] ?? ''; $info->transaction = $payment_response['transaction'] ?? ''; $info->trxref = $payment_response['trxref'] ?? ''; $info->amount = ($reqs['data']['amount'] / 100) ?? ''; $info->customer_code = $reqs['data']['customer']['customer_code'] ?? ''; $info->plan_code = ($reqs['data']['plan'] ?? '') . ' / ' . $planId; $info->currency = $reqs['data']['currency'] ?? ''; $info->other = $reqs['data']['paidAt'] ?? ''; $info->save(); if ($payment != null) { $total = $plan->price; $taxValue = taxToVal(($plan->price - $gateway->tax), $gateway->tax); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $coupon->usersUsed()->attach($user->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $subscription = new Subscriptions; // if its lifetime subscription if ($billingPlanId == 'Not Needed') { $subscription->stripe_id = 'PSLS-' . strtoupper(Str::random(13)); $subscription->stripe_price = $product->price_id; $subscription->ends_at = $plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value ? \Carbon\Carbon::now()->addMonths(1) : \Carbon\Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->stripe_status = 'paystack_approved'; } else { $bill_customer_id = $reqs['data']['customer']['id']; $bill_plan_id = $reqs['data']['plan_object']['id']; $subscription_billing = self::curl_req_get(self::$subscription_endpoint . '?customer=' . $bill_customer_id . '&plan=' . $bill_plan_id, $key); if ($subscription_billing['status'] == false) { // if something went wrong with the request abort(404); } $subscription_billing_code = $subscription_billing['data'][0]['subscription_code']; $subscription->stripe_id = $subscription_billing_code; $subscription->stripe_price = $billingPlanId; $subscription->stripe_status = 'active'; $subscription->ends_at = $plan->trial_days != 0 ? \Carbon\Carbon::now()->addDays($plan->trial_days) : \Carbon\Carbon::now()->addDays(30); } $subscription->user_id = $user->id; $subscription->name = $planId; $subscription->quantity = 1; $subscription->plan_id = $planId; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->tax_rate = $gateway->tax; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $total; $subscription->save(); $payment->status = 'Success'; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Subscribed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); DB::commit(); if (class_exists('App\Extensions\Affilate\System\Events\AffiliateEvent')) { event(new \App\Extensions\Affilate\System\Events\AffiliateEvent($total, $gateway->currency)); } return redirect()->route('dashboard.' . $user->type->value . '.index')->with(['message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success']); } else { DB::rollBack(); $msg = 'PaystackController::subscribePay(): Could not find required payment order!'; Log::error($msg); return redirect()->route('dashboard.' . $user->type->value . '.index')->with(['message' => $msg, 'type' => 'error']); } } catch (Exception $th) { DB::rollBack(); Log::error('PaystackController::subscribePay(): ' . $th->getMessage()); return redirect()->route('dashboard.' . $user->type->value . '.index')->with(['message' => $th->getMessage(), 'type' => 'error']); } } // tested public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $newDiscountedPrice = $plan->price; $coupone = checkCouponInRequest(); if ($coupone) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupone->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } // remove tax when coupon is applied because its included in the plan price. $newDiscountedPrice -= $taxValue; } $currency = Currency::where('id', $gateway->currency)->first()->code; $orderId = null; $exception = null; if (self::getPaystackProductId($plan->id) == null) { $exception = 'Product ID is not set! Please save Membership Plan again.'; } } catch (Exception $th) { $exception = Str::before($th->getMessage(), ':'); } return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'orderId', 'gateway', 'exception', 'currency')); } // tested public static function prepaidCheckout(Request $request) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { DB::beginTransaction(); $previousRequest = app('request')->create(url()->previous()); $payment_response = json_decode($request->response, true); $payment_response_reference = $payment_response['reference']; if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } // verify transaction with paystack if it was successful then continue $reqs = self::curl_req_get(self::$transaction_verify_endpoint . $payment_response_reference, $key); if ($reqs['status'] == false) { // if something went wrong with the request abort(404); } // failed or success if ($reqs['data']['status'] != 'success') { // if the transaction was not successful abort(400, $reqs['data']['gateway_response']); } $user = auth()->user(); // log the transaction data to database $info = new PaystackPaymentInfo; $info->user_id = $user->id; $info->email = $user->email; $info->reference = $payment_response['reference'] ?? ''; $info->trans = $payment_response['trans'] ?? ''; $info->status = $payment_response['status'] ?? ''; $info->message = $payment_response['message'] ?? ''; $info->transaction = $payment_response['transaction'] ?? ''; $info->trxref = $payment_response['trxref'] ?? ''; $info->amount = ($reqs['data']['amount'] / 100) ?? ''; $info->customer_code = $reqs['data']['customer']['customer_code'] ?? ''; $info->plan_code = ($reqs['data']['plan'] ?? '') . ' / ' . $request->planID; $info->currency = $reqs['data']['currency'] ?? ''; $info->other = $reqs['data']['paidAt'] ?? ''; $info->save(); $plan = Plan::find($request->planID); $user = Auth::user(); $settings = Setting::getCache(); $newDiscountedPrice = $plan->price; if ($previousRequest->has('coupon')) { $coupon = Coupon::where('code', $previousRequest->input('coupon'))->first(); if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); $coupon->usersUsed()->attach($user->id); } } $payment = new UserOrder; $payment->order_id = Str::random(12); $payment->plan_id = $plan->id; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = 'Credit, Debit Card'; $payment->price = $newDiscountedPrice; $payment->affiliate_earnings = ($newDiscountedPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Token Pack')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($newDiscountedPrice); DB::commit(); return redirect()->route('dashboard.' . $user->type->value . '.index')->with(['message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success']); } catch (Exception $th) { DB::rollBack(); Log::error('PaystackController::subscribePay(): ' . $th->getMessage()); return redirect()->route('dashboard.' . $user->type->value . '.index')->with(['message' => $th->getMessage(), 'type' => 'error']); } } // tested public static function getSubscriptionDaysLeft() { $gateway = Gateways::where('code', 'paystack')->first(); if ($gateway == null) { return null; } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } $user = auth()->user(); // Get current active subscription $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { if ($activeSub->stripe_price != 'Not Needed') { $reqs = self::curl_req_get(self::$subscription_endpoint . '/' . $activeSub->stripe_id, $key); if ($reqs['status'] == false) { // if something went wrong with the request Log::error("PaystackController::getSubscriptionRenewDate() :\n" . json_encode($reqs)); return back()->with(['message' => 'Paystack Gateway : ' . json_encode($reqs), 'type' => 'error']); } if (isset($reqs['data']['next_payment_date'])) { // return \Carbon\Carbon::parse($reqs['data']['next_payment_date'])->format('F jS, Y'); return \Carbon\Carbon::now()->diffInDays($reqs['data']['next_payment_date']); } } else { return \Carbon\Carbon::now()->diffInDays(\Carbon\Carbon::parse($activeSub->ends_at)); } } return null; } // tested public static function getSubscriptionRenewDate() { $gateway = Gateways::where('code', 'paystack')->first(); if ($gateway == null) { return null; } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } $user = auth()->user(); // Get current active subscription $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { if ($activeSub->stripe_price != 'Not Needed') { $reqs = self::curl_req_get(self::$subscription_endpoint . '/' . $activeSub->stripe_id, $key); if ($reqs['status'] == false) { // if something went wrong with the request Log::error("PaystackController::getSubscriptionRenewDate() :\n" . json_encode($reqs)); return back()->with(['message' => 'Paystack Gateway : ' . json_encode($reqs), 'type' => 'error']); } if (isset($reqs['data']['next_payment_date'])) { return \Carbon\Carbon::parse($reqs['data']['next_payment_date'])->format('F jS, Y'); } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); return \Carbon\Carbon::now()->format('F jS, Y'); } } else { return \Carbon\Carbon::createFromTimeStamp($activeSub->ends_at)->format('F jS, Y'); } } return null; } // tested public static function getSubscriptionStatus() { $gateway = Gateways::where('code', 'paystack')->first(); if ($gateway == null) { return null; } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } $user = auth()->user(); $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { if ($activeSub->stripe_price != 'Not Needed') { $reqs = self::curl_req_get(self::$subscription_endpoint . '/' . $activeSub->stripe_id, $key); if ($reqs['status'] == false) { // if something went wrong with the request Log::error("PaystackController::getSubscriptionStatus() :\n" . json_encode($reqs)); return back()->with(['message' => 'Paystack Gateway : ' . json_encode($reqs), 'type' => 'error']); } if ($reqs['data']['status'] == 'active') { return true; } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); return false; } } else { return true; } } return null; } // tested public static function cancelSubscribedPlan($planId, $subsId) { $currentSubscription = Subscriptions::where('id', $subsId)->first(); if ($currentSubscription != null) { $plan = Plan::where('id', $planId)->first(); $gateway = Gateways::where('code', 'paystack')->first(); if ($gateway == null) { return null; } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } if ($currentSubscription->stripe_price != 'Not Needed') { $get_subscribe_info = self::curl_req_get(self::$cancelSubscribedPlan . '/' . $currentSubscription->stripe_id, $key); if ($get_subscribe_info['status'] == false) { // if something went wrong with the request Log::error("PaystackController::cancelSubscribedPlan() :\n" . json_encode($get_subscribe_info)); return back()->with(['message' => 'Paystack Gateway : ' . json_encode($get_subscribe_info), 'type' => 'error']); } $request = self::curl_req(self::$subscription_endpoint . '/disable', $key, [ 'code' => $currentSubscription->stripe_id, 'token' => $get_subscribe_info['data']['email_token'], ]); if ($request['status'] == true && $request['message'] == 'Subscription disabled successfully') { $currentSubscription->stripe_status = 'cancelled'; $currentSubscription->ends_at = \Carbon\Carbon::now(); $currentSubscription->save(); return true; } } else { $currentSubscription->stripe_status = 'cancelled'; $currentSubscription->ends_at = \Carbon\Carbon::now(); $currentSubscription->save(); return true; } } return false; } // tested public static function subscribeCancel() { $user = auth()->user(); // Get current active subscription $activeSub = getCurrentActiveSubscription($user->id); if ($activeSub != null) { $plan = Plan::where('id', $activeSub->plan_id)->first(); $gateway = Gateways::where('code', 'paystack')->first(); if ($gateway == null) { abort(404); } if ($gateway->mode == 'sandbox') { $key = $gateway->sandbox_client_secret; } else { $key = $gateway->live_client_secret; } if ($activeSub->stripe_price != 'Not Needed') { $reqs = self::curl_req_get(self::$subscription_endpoint . '/' . $activeSub->stripe_id, $key); if ($reqs['status'] == false) { // if something went wrong with the request abort(404, $reqs['message']); } $mailToken = $reqs['data']['email_token']; $request = self::curl_req(self::$subscription_endpoint . '/disable', $key, [ 'code' => $activeSub->stripe_id, 'token' => $mailToken, ]); if ($request['status'] == true && $request['message'] == 'Subscription disabled successfully') { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } else { Log::error('PaystackController::disableOldSubscriptionAndReturnNew(): ' . $request['message']); return back()->with(['message' => __('Your subscription could not cancelled.'), 'type' => 'error']); } } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } // tested private static function isValidWebhookSignature($input, $secret, $signature) { $expectedSignature = hash_hmac('sha512', $input, $secret); return hash_equals($expectedSignature, $signature); } // tested public static function handleWebhook(Request $request) { $input = $request->getContent(); $secret = self::getKey(); // Validate the Paystack webhook signature if (self::isValidWebhookSignature($input, $secret, $request->header('HTTP_X_PAYSTACK_SIGNATURE'))) { // Handle the webhook events $payload = json_decode($input, true); event(new PaystackWebhookEvent($payload)); } // Invalid signature return response()->json(['status' => 'error'], 400); } // tested /** * curl post request template */ public static function curl_req($second_url, $key, $data = []) { $fields_string = http_build_query($data); // open connection $ch = curl_init(); // set the url, number of POST vars, POST data curl_setopt($ch, CURLOPT_URL, self::$client . $second_url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $key, 'Cache-Control: no-cache', ]); // So that curl_exec returns the contents of the cURL; rather than echoing it curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // execute post $request = curl_exec($ch); curl_close($ch); if ($request) { $result = json_decode($request, true); if (isset($result['status']) && $result['status'] !== true) { abort(400, 'Paystack: ' . $result['message']); } return $result; } else { abort(400, $result); } } // tested /** * curl get request template */ public static function curl_req_get($param, $key) { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => self::$client . $param, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $key, 'Cache-Control: no-cache', ], ]); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if ($err) { abort(400, 'Paystack: ' . $err); } else { $result = json_decode($response, true); if (isset($result['status']) && $result['status'] !== true) { abort(400, 'Paystack: ' . $result['message']); } return $result; } } // tested /** * Reads GatewayProducts table and returns price id of the given plan */ public static function getPaystackPriceId($planId) { // check if plan exists $plan = Plan::where('id', $planId)->first(); if ($plan != null) { $product = GatewayProducts::where(['plan_id' => $planId, 'gateway_code' => 'paystack'])->first(); if ($product != null) { return $product->price_id; } else { return null; } } return null; } // tested /** * Reads GatewayProducts table and returns price id of the given plan */ public static function getPaystackProductId($planId) { // check if plan exists $plan = Plan::where('id', $planId)->first(); if ($plan != null) { $product = GatewayProducts::where(['plan_id' => $planId, 'gateway_code' => 'paystack'])->first(); if ($product != null) { return $product->product_id; } else { return null; } } return null; } // tested /** * get key if sadbox or live */ private static function getKey($gateway = null) { $theGateway = $gateway ?? Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); if ($theGateway->mode == 'sandbox') { $key = $theGateway->sandbox_client_secret; } else { $key = $theGateway->live_client_secret; } return $key; } // tested /** * Since price id (billing plan) is changed, we must update user data, i.e cancel current subscriptions. */ public static function updateUserData() { $key = self::getKey(); try { $history = OldGatewayProducts::where(['gateway_code' => self::$GATEWAY_CODE, 'status' => 'check'])->get(); if ($history != null) { foreach ($history as $record) { // check record current status from gateway $lookingFor = $record->old_price_id; // billingPlan id in paystack // get also subscription id and customer id and mail token // search subscriptions for record $subs = Subscriptions::where('paid_with', self::$GATEWAY_CODE) ->where('stripe_status', 'active') ->where('stripe_price', $lookingFor) ->get(); foreach ($subs ?? [] as $sub) { $subscriptionId = $sub->stripe_id; $reqs = self::curl_req_get(self::$subscription_endpoint . '/' . $subscriptionId, $key); if ($reqs['status'] == false) { // if something went wrong with the request abort(404); } $mailToken = $reqs['data']['email_token']; $customerId = $reqs['data']['customer']['customer_code']; $planId = $reqs['data']['plan']['plan_code']; // cancel old subscription from gateway $new_subscription_code = self::disableOldSubscriptionAndReturnNew($subscriptionId, $mailToken, $customerId, $planId); if ($new_subscription_code == false) { Log::error('PaystackService::updateUserData(): Could not create new subscription for user: ' . $sub->user_id); continue; } $sub->stripe_id = $new_subscription_code; $sub->save(); } $record->status = 'checked'; $record->save(); } } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . "-> updateUserData():\n" . $ex->getMessage()); return ['result' => Str::before($ex->getMessage(), ':')]; } } // tested public static function disableOldSubscriptionAndReturnNew($subscriptionId, $mail_token, $customerID, $planID) { $key = self::getKey(); $request = self::curl_req(self::$subscription_endpoint . '/disable', $key, [ 'code' => $subscriptionId, 'token' => $mail_token, ]); if ($request['status'] == true && $request['message'] == 'Subscription disabled successfully') { // create new subscription insted of old one $req = self::curl_req(self::$subscription_endpoint, $key, [ 'customer' => $customerID, 'plan' => $planID, ]); if ($req['status'] == false) { Log::error('PaystackService::disableOldSubscriptionAndReturnNew(): ' . $req['message']); return false; } return $req['data']['subscription_code']; } else { Log::error('PaystackService::disableOldSubscriptionAndReturnNew(): ' . $request['message']); return false; } } // tested public static function checkIfTrial() { return false; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'paystack', 'title' => 'Paystack', 'link' => 'https://paystack.com/', 'active' => 0, 'available' => 1, 'img' => '/assets/img/payments/paystack-2.svg', 'whiteLogo' => 0, 'mode' => 1, 'sandbox_client_id' => 1, 'sandbox_client_secret' => 1, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 1, 'live_app_id' => 0, 'currency' => 1, 'currency_locale' => 0, 'notify_url' => 0, 'base_url' => 0, 'sandbox_url' => 0, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } CoinBaseService.php000064400000000111152667044740010271 0ustar00config = new \Iyzipay\Options; $this->config->setApiKey($apiKey); $this->config->setSecretKey($apiSecretKey); $this->config->setBaseUrl($baseUrl); $this->locale = $locale; $this->currency = $currency; // Log::info("iyzipayActions constructor called : " . $apiKey . " " . $apiSecretKey . " " . $baseUrl . " " . $locale . " " . $currency); } // get config public function getConfig() { return $this->config; } // get locale public function getLocale() { return $this->locale; } // generate random string with given length public function generateRandomString($length = 12) { return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length); } // generate random number with given length public function generateRandomNumber($length = 12) { return substr(str_shuffle(str_repeat($x = '0123456789', ceil($length / strlen($x)))), 1, $length); } // ************************************************ // ******* SUBSCRIPTION CUSTOMER ACTIONS ********** // ************************************************ // create customer for \Iyzipay\Model\Customer public function createCustomer($request) { $customer = new \Iyzipay\Model\Customer; $customer->setName($request->name ?? ''); $customer->setSurname($request->surname ?? ''); $customer->setGsmNumber($request->gsmNumber ?? ''); $customer->setEmail($request->email ?? ''); $customer->setIdentityNumber($request->identityNumber ?? ''); $customer->setShippingContactName($request->shippingContactName ?? ''); $customer->setShippingCity($request->shippingCity ?? ''); $customer->setShippingCountry($request->shippingCountry ?? ''); $customer->setShippingAddress($request->shippingAddress ?? ''); $customer->setShippingZipCode($request->shippingZipCode ?? ''); $customer->setBillingContactName($request->billingContactName ?? ''); $customer->setBillingCity($request->billingCity ?? ''); $customer->setBillingCountry($request->billingCountry ?? ''); $customer->setBillingAddress($request->billingAddress ?? ''); $customer->setBillingZipCode($request->billingZipCode ?? ''); return $customer; } // create a subscription customer for \Iyzipay\Request\Subscription\SubscriptionCreateCustomerRequest public function createSubscriptionCustomer($request) { $requestSubscriptionCustomer = new \Iyzipay\Request\Subscription\SubscriptionCreateCustomerRequest; $requestSubscriptionCustomer->setLocale($this->locale); $requestSubscriptionCustomer->setConversationId(self::generateRandomNumber()); $customer = new \Iyzipay\Model\Customer; $customer->setName($request->name ?? ''); $customer->setSurname($request->surname ?? ''); $customer->setGsmNumber($request->gsmNumber ?? ''); $customer->setEmail($request->email ?? ''); $customer->setIdentityNumber($request->identityNumber ?? ''); $customer->setShippingContactName($request->shippingContactName ?? ''); $customer->setShippingCity($request->shippingCity ?? ''); $customer->setShippingCountry($request->shippingCountry ?? ''); $customer->setShippingAddress($request->shippingAddress ?? ''); $customer->setShippingZipCode($request->shippingZipCode ?? ''); $customer->setBillingContactName($request->billingContactName ?? ''); $customer->setBillingCity($request->billingCity ?? ''); $customer->setBillingCountry($request->billingCountry ?? ''); $customer->setBillingAddress($request->billingAddress ?? ''); $customer->setBillingZipCode($request->billingZipCode ?? ''); $requestSubscriptionCustomer->setCustomer($customer); $subscriptionCustomer = \Iyzipay\Model\Subscription\SubscriptionCustomer::create($requestSubscriptionCustomer, $this->config); return $subscriptionCustomer; } // retrieve a subscription customer for \Iyzipay\Request\Subscription\SubscriptionRetrieveCustomerRequest public function retrieveSubscriptionCustomer($request) { $requestSubscriptionCustomer = new \Iyzipay\Request\Subscription\SubscriptionRetrieveCustomerRequest; $requestSubscriptionCustomer->setLocale($this->locale); $requestSubscriptionCustomer->setConversationId(self::generateRandomNumber()); $requestSubscriptionCustomer->setCustomerReferenceCode($request->customerReferenceCode); $subscriptionCustomer = \Iyzipay\Model\Subscription\SubscriptionCustomer::retrieve($requestSubscriptionCustomer, $this->config); return $subscriptionCustomer; } // update a subscription customer for \Iyzipay\Request\Subscription\SubscriptionUpdateCustomerRequest public function updateSubscriptionCustomer($request) { $requestSubscriptionCustomer = new \Iyzipay\Request\Subscription\SubscriptionUpdateCustomerRequest; $requestSubscriptionCustomer->setLocale($this->locale); $requestSubscriptionCustomer->setConversationId(self::generateRandomNumber()); $requestSubscriptionCustomer->setCustomerReferenceCode($request->customerReferenceCode); $customer = new \Iyzipay\Model\Customer; $customer->setName($request->name ?? ''); $customer->setSurname($request->surname ?? ''); $customer->setGsmNumber($request->gsmNumber ?? ''); $customer->setEmail($request->email ?? ''); $customer->setIdentityNumber($request->identityNumber ?? ''); $customer->setShippingContactName($request->shippingContactName ?? ''); $customer->setShippingCity($request->shippingCity ?? ''); $customer->setShippingCountry($request->shippingCountry ?? ''); $customer->setShippingDistrict($request->shippingDistrict ?? ''); $customer->setShippingAddress($request->shippingAddress ?? ''); $customer->setShippingZipCode($request->shippingZipCode ?? ''); $customer->setBillingContactName($request->billingContactName ?? ''); $customer->setBillingCity($request->billingCity ?? ''); $customer->setBillingCountry($request->billingCountry ?? ''); $customer->setBillingDistrict($request->billingDistrict ?? ''); $customer->setBillingAddress($request->billingAddress ?? ''); $customer->setBillingZipCode($request->billingZipCode ?? ''); $requestSubscriptionCustomer->setCustomer($customer); $subscriptionCustomer = \Iyzipay\Model\Subscription\SubscriptionCustomer::update($requestSubscriptionCustomer, $this->config); return $subscriptionCustomer; } // delete a subscription customer for \Iyzipay\Request\Subscription\SubscriptionDeleteCustomerRequest public function deleteSubscriptionCustomer($request) { $requestSubscriptionCustomer = new \Iyzipay\Request\Subscription\SubscriptionDeleteCustomerRequest; $requestSubscriptionCustomer->setLocale($this->locale); $requestSubscriptionCustomer->setConversationId(self::generateRandomNumber()); $requestSubscriptionCustomer->setCustomerReferenceCode($request->customerReferenceCode); $subscriptionCustomer = \Iyzipay\Model\Subscription\SubscriptionCustomer::delete($requestSubscriptionCustomer, $this->config); return $subscriptionCustomer; } // ************************************************ // ******** SUBSCRIPTION PRODUCT ACTIONS ********** // ************************************************ // create a subscription product for \Iyzipay\Request\Subscription\SubscriptionCreateProductRequest public function createSubscriptionProduct($request) { $requestSubscriptionProduct = new \Iyzipay\Request\Subscription\SubscriptionCreateProductRequest; $requestSubscriptionProduct->setLocale($this->locale); $requestSubscriptionProduct->setConversationId(self::generateRandomNumber()); $requestSubscriptionProduct->setName($request->name); $requestSubscriptionProduct->setDescription($request->description ?? ''); $subscriptionProduct = \Iyzipay\Model\Subscription\SubscriptionProduct::create($requestSubscriptionProduct, $this->config); return $subscriptionProduct; } // retrieve a subscription product for \Iyzipay\Request\Subscription\SubscriptionRetrieveProductRequest public function retrieveSubscriptionProduct($request) { $requestSubscriptionProduct = new \Iyzipay\Request\Subscription\SubscriptionRetrieveProductRequest; $requestSubscriptionProduct->setLocale($this->locale); $requestSubscriptionProduct->setConversationId(self::generateRandomNumber()); $requestSubscriptionProduct->setProductReferenceCode($request->productReferenceCode); $subscriptionProduct = \Iyzipay\Model\Subscription\SubscriptionProduct::retrieve($requestSubscriptionProduct, $this->config); return $subscriptionProduct; } // update a subscription product for \Iyzipay\Request\Subscription\SubscriptionUpdateProductRequest public function updateSubscriptionProduct($request) { $requestSubscriptionProduct = new \Iyzipay\Request\Subscription\SubscriptionUpdateProductRequest; $requestSubscriptionProduct->setLocale($this->locale); $requestSubscriptionProduct->setConversationId(self::generateRandomNumber()); $requestSubscriptionProduct->setProductReferenceCode($request->productReferenceCode); $requestSubscriptionProduct->setName($request->name); $requestSubscriptionProduct->setDescription($request->description ?? ''); $subscriptionProduct = \Iyzipay\Model\Subscription\SubscriptionProduct::update($requestSubscriptionProduct, $this->config); return $subscriptionProduct; } // delete a subscription product for \Iyzipay\Request\Subscription\SubscriptionDeleteProductRequest public function deleteSubscriptionProduct($request) { $requestSubscriptionProduct = new \Iyzipay\Request\Subscription\SubscriptionDeleteProductRequest; $requestSubscriptionProduct->setLocale($this->locale); $requestSubscriptionProduct->setConversationId(self::generateRandomNumber()); $requestSubscriptionProduct->setProductReferenceCode($request->productReferenceCode); $subscriptionProduct = \Iyzipay\Model\Subscription\SubscriptionProduct::delete($requestSubscriptionProduct, $this->config); return $subscriptionProduct; } // get all subscription products for \Iyzipay\Request\Subscription\SubscriptionListProductsRequest public function listSubscriptionProducts($request) { $requestSubscriptionProduct = new \Iyzipay\Request\Subscription\SubscriptionListProductsRequest; $requestSubscriptionProduct->setPage($request->itemPage ?? 1); $requestSubscriptionProduct->setCount($request->itemCount ?? 50); $subscriptionProduct = \Iyzipay\Model\Subscription\RetrieveList::products($requestSubscriptionProduct, $this->config); return $subscriptionProduct; } // ***************************************************************** // ************ SUBSCRIPTION PRICING PLAN ACTIONS ****************** // ***************************************************************** // create a subscription pricing plan for \Iyzipay\Request\Subscription\SubscriptionCreatePricingPlanRequest public function createSubscriptionPricingPlan($request) { $requestSubscriptionPricingPlan = new \Iyzipay\Request\Subscription\SubscriptionCreatePricingPlanRequest; $requestSubscriptionPricingPlan->setLocale($this->locale); $requestSubscriptionPricingPlan->setConversationId(self::generateRandomNumber()); $requestSubscriptionPricingPlan->setProductReferenceCode($request->productReferenceCode); $requestSubscriptionPricingPlan->setName($request->name ?? ''); $requestSubscriptionPricingPlan->setCurrencyCode($this->currency); $requestSubscriptionPricingPlan->setPrice($request->price ?? 1); $requestSubscriptionPricingPlan->setPaymentInterval($request->paymentInterval ?? 'MONTHLY'); $requestSubscriptionPricingPlan->setPaymentIntervalCount($request->paymentIntervalCount ?? 1); $requestSubscriptionPricingPlan->setPlanPaymentType($request->paymentType ?? 'RECURRING'); $requestSubscriptionPricingPlan->setTrialPeriodDays($request->trialPeriodDays ?? 0); // $requestSubscriptionPricingPlan->setRecurrenceCount($request->recurrenceCount ?? 0); $subscriptionPricingPlan = \Iyzipay\Model\Subscription\SubscriptionPricingPlan::create($requestSubscriptionPricingPlan, $this->config); return $subscriptionPricingPlan; } // retrieve a subscription pricing plan for \Iyzipay\Request\Subscription\SubscriptionRetrievePricingPlanRequest public function retrieveSubscriptionPricingPlan($request) { $requestSubscriptionPricingPlan = new \Iyzipay\Request\Subscription\SubscriptionRetrievePricingPlanRequest; $requestSubscriptionPricingPlan->setLocale($this->locale); $requestSubscriptionPricingPlan->setConversationId(self::generateRandomNumber()); $requestSubscriptionPricingPlan->setPricingPlanReferenceCode($request->pricingPlanReferenceCode); $subscriptionPricingPlan = \Iyzipay\Model\Subscription\SubscriptionPricingPlan::retrieve($requestSubscriptionPricingPlan, $this->config); return $subscriptionPricingPlan; } // update a subscription pricing plan for \Iyzipay\Request\Subscription\SubscriptionUpdatePricingPlanRequest public function updateSubscriptionPricingPlan($request) { $requestSubscriptionPricingPlan = new \Iyzipay\Request\Subscription\SubscriptionUpdatePricingPlanRequest; $requestSubscriptionPricingPlan->setLocale($this->locale); $requestSubscriptionPricingPlan->setConversationId(self::generateRandomNumber()); $requestSubscriptionPricingPlan->setPricingPlanReferenceCode($request->pricingPlanReferenceCode); $requestSubscriptionPricingPlan->setName($request->name ?? ''); $requestSubscriptionPricingPlan->setTrialPeriodDays($request->trialPeriodDays ?? ''); $subscriptionPricingPlan = \Iyzipay\Model\Subscription\SubscriptionPricingPlan::update($requestSubscriptionPricingPlan, $this->config); return $subscriptionPricingPlan; } // delete a subscription pricing plan for \Iyzipay\Request\Subscription\SubscriptionDeletePricingPlanRequest public function deleteSubscriptionPricingPlan($request) { $requestSubscriptionPricingPlan = new \Iyzipay\Request\Subscription\SubscriptionDeletePricingPlanRequest; $requestSubscriptionPricingPlan->setLocale($this->locale); $requestSubscriptionPricingPlan->setConversationId(self::generateRandomNumber()); $requestSubscriptionPricingPlan->setPricingPlanReferenceCode($request->pricingPlanReferenceCode); $subscriptionPricingPlan = \Iyzipay\Model\Subscription\SubscriptionPricingPlan::delete($requestSubscriptionPricingPlan, $this->config); return $subscriptionPricingPlan; } // ************************************************ // *************** BUYER ACTIONS ****************** // ************************************************ // create a buyer for \Iyzipay\Model\Buyer public function createBuyer($request) { $buyer = new \Iyzipay\Model\Buyer; $buyer->setId($request->id); $buyer->setName($request->name ?? ''); $buyer->setSurname($request->surname ?? ''); $buyer->setIdentityNumber($request->identityNumber ?? ''); $buyer->setEmail($request->email ?? ''); $buyer->setGsmNumber($request->gsmNumber ?? ''); // $buyer->setRegistrationDate($request->registrationDate); //was optional so not added // $buyer->setLastLoginDate($request->lastLoginDate); //was optional so not added $buyer->setRegistrationAddress($request->registrationAddress ?? ''); $buyer->setCity($request->city ?? ''); $buyer->setCountry($request->country ?? ''); $buyer->setZipCode($request->zipCode ?? ''); $buyer->setIp($request->ip ?? ''); return $buyer; } // ************************************************ // ************ BASKET ITEM ACTIONS ****************** // ************************************************ // create a basket item for \Iyzipay\Model\BasketItem public function createBasketItem($request) { $basketItem = new \Iyzipay\Model\BasketItem; $basketItem->setId($request['basketItemId']); $basketItem->setName($request['name']); $basketItem->setCategory1($request['category1'] ?? ''); // $basketItem->setCategory2($request['category2'] ?? ""); $basketItem->setItemType($request['itemType'] ?? ''); $basketItem->setPrice($request['price'] ?? ''); return $basketItem; } // ************************************************ // ************ ADDRESS ACTIONS ****************** // ************************************************ // create a address for \Iyzipay\Model\Address public function createAddress($request) { $address = new \Iyzipay\Model\Address; $address->setContactName($request->contactName ?? ''); $address->setCity($request->city ?? ''); $address->setCountry($request->country ?? ''); $address->setAddress($request->address ?? ''); $address->setZipCode($request->zipCode ?? ''); return $address; } // ************************************************ // ************ ONE TIME PAYMENT ACTIONS ****************** // ************************************************ // create a one time payment with \Iyzipay\Request\CreateCheckoutFormInitializeRequest public function createOneTimePayment($request) { $requestOneTimePayment = new \Iyzipay\Request\CreateCheckoutFormInitializeRequest; $requestOneTimePayment->setLocale($this->locale); $requestOneTimePayment->setConversationId(self::generateRandomNumber()); $requestOneTimePayment->setPrice($request->price ?? ''); $requestOneTimePayment->setPaidPrice($request->paidPrice ?? ''); $requestOneTimePayment->setCurrency($this->currency ?? ''); $requestOneTimePayment->setBasketId($request->basketId ?? ''); // $requestOneTimePayment->setPaymentGroup($request->paymentGroup ?? ""); // Optional so not used // $requestOneTimePayment->setPaymentSource($request->paymentSource ?? ""); // Optional so not used $requestOneTimePayment->setCallbackUrl($request->callbackUrl ?? ''); $requestOneTimePayment->setEnabledInstallments($request->enabledInstallments ?? ''); // $requestOneTimePayment->setDebitCardAllowed($request->debitCardAllowed ?? ""); // not in example page $requestOneTimePayment->setBuyer($request->buyer ?? ''); $requestOneTimePayment->setShippingAddress($request->shippingAddress ?? ''); $requestOneTimePayment->setBillingAddress($request->billingAddress ?? ''); $requestOneTimePayment->setBasketItems($request->basketItems ?? ''); $checkoutFormInitialize = \Iyzipay\Model\CheckoutFormInitialize::create($requestOneTimePayment, $this->config); return $checkoutFormInitialize; } // retrieve a one time payment result with \Iyzipay\Request\RetrieveCheckoutFormRequest public function retrieveOneTimePayment($request) { $requestOneTimePayment = new \Iyzipay\Request\RetrieveCheckoutFormRequest; $requestOneTimePayment->setLocale($this->locale); $requestOneTimePayment->setConversationId(self::generateRandomNumber()); $requestOneTimePayment->setToken($request['token']); $checkoutForm = \Iyzipay\Model\CheckoutForm::retrieve($requestOneTimePayment, $this->config); return $checkoutForm; } // ************************************************ // ************ SUBSCRIPTION ACTIONS ****************** // ************************************************ // create a subscription with \Iyzipay\Request\Subscription\SubscriptionCreateCheckoutFormRequest public function createSubscription($request) { $requestSubscription = new \Iyzipay\Request\Subscription\SubscriptionCreateCheckoutFormRequest; $requestSubscription->setLocale($this->locale); $requestSubscription->setConversationId(self::generateRandomNumber()); $requestSubscription->setCallbackUrl($request->callbackUrl); $requestSubscription->setPricingPlanReferenceCode($request->pricingPlanReferenceCode); $requestSubscription->setSubscriptionInitialStatus($request->subscriptionInitialStatus); $requestSubscription->setCustomer($request->customer); $checkoutFormInitialize = \Iyzipay\Model\Subscription\SubscriptionCheckoutForm::create($requestSubscription, $this->config); return $checkoutFormInitialize; } // get details of a subscription with \Iyzipay\Request\Subscription\SubscriptionDetailsRequest public function getSubscriptionDetails($request) { $requestSubscriptionDetails = new \Iyzipay\Request\Subscription\SubscriptionDetailsRequest; $requestSubscriptionDetails->setLocale($this->locale); $requestSubscriptionDetails->setConversationId(self::generateRandomNumber()); $requestSubscriptionDetails->setSubscriptionReferenceCode($request->subscriptionReferenceCode); $subscriptionDetails = \Iyzipay\Model\Subscription\SubscriptionDetails::retrieve($requestSubscriptionDetails, $this->config); return $subscriptionDetails; } // cancel a subscription with \Iyzipay\Request\Subscription\SubscriptionCancelRequest public function cancelSubscription($request) { $requestSubscriptionCancel = new \Iyzipay\Request\Subscription\SubscriptionCancelRequest; $requestSubscriptionCancel->setLocale($this->locale); $requestSubscriptionCancel->setConversationId(self::generateRandomNumber()); $requestSubscriptionCancel->setSubscriptionReferenceCode($request->subscriptionReferenceCode); $subscriptionCancel = \Iyzipay\Model\Subscription\SubscriptionCancel::cancel($requestSubscriptionCancel, $this->config); return $subscriptionCancel; } // ************************************************ // ************ WEBHOOKS ACTIONS ****************** // ************************************************ // TODO: Will be implemented later } YokassaService.php000064400000062020152667044740010227 0ustar00where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $user = Auth::user(); if ($gateway->mode == 'sandbox') { $shop_id = $gateway->sandbox_client_id; $key = $gateway->sandbox_client_secret; } else { $shop_id = $gateway->live_client_id; $key = $gateway->live_client_secret; } try { $client = new Client; $client->setAuth($shop_id, $key); $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $newDiscountedPrice = $plan->price; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $currency = Currency::where('id', $gateway->currency)->first()->code; if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); $newDiscountedPriceCents = (int) (((float) $newDiscountedPrice) * 100); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } $newDiscountedPrice += $taxValue; $payment = $client->createPayment( [ 'amount' => [ 'value' => $newDiscountedPrice, 'currency' => $currency, ], 'confirmation' => [ 'type' => 'embedded', ], 'capture' => true, 'description' => 'Order No. 1', 'save_payment_method' => true, ], uniqid('', true) ); $confirmation_token = $payment->confirmation->confirmation_token; $payment_id = $payment->id; return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'payment_id', 'confirmation_token')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $couponID = $request->input('couponID', null); $paymentId = $request->input('paymentID', null); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; if ($gateway->mode == 'sandbox') { $shop_id = $gateway->sandbox_client_id; $key = $gateway->sandbox_client_secret; } else { $shop_id = $gateway->live_client_id; $key = $gateway->live_client_secret; } $user = Auth::user(); $settings = Setting::getCache(); try { DB::beginTransaction(); $plan = Plan::where('id', $planID)->first(); $client = new Client; $client->setAuth($shop_id, $key); $payment = $client->getPaymentInfo($paymentId); $total = $plan->price; $taxValue = taxToVal($plan->price, $gateway->tax); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $coupon->usersUsed()->attach(auth()->user()->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $total += $taxValue; if ($payment->paid == true) { $paymentMethod = $payment->payment_method->type; $payment_method_id = $payment->payment_method->id; // new subscription $subscription = new YokassaSubscription; $subscription->plan_id = $plan->id; $subscription->user_id = $user->id; $subscription->name = $plan->id; $subscription->payment_method_id = $payment_method_id; if ($plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value || $plan->frequency == FrequencyEnum::LIFETIME_YEARLY->value) { $subscription->next_pay_at = $plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value ? \Carbon\Carbon::now()->addMonths(1) : \Carbon\Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->subscription_status = 'yokassa_approved'; } else { $subscription->subscription_status = 'active'; $subscription->next_pay_at = $plan->trial_days != 0 ? \Carbon\Carbon::now()->addDays($plan->trial_days) : \Carbon\Carbon::now()->addDays(30); } $subscription->tax_rate = $gateway->tax; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $total; $subscription->save(); // new order $payment = new UserOrder; $payment->order_id = $subscription->payment_method_id; $payment->plan_id = $plan->id; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $total; $payment->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $gateway->tax; $payment->tax_value = $taxValue; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Subscribed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); DB::commit(); } else { DB::rollBack(); return redirect()->route('dashboard.' . auth()->user()->type . '.index')->with(['message' => __('You are failed your purchase. If you paid for this, please cantact us'), 'type' => 'failed']); } return redirect()->route('dashboard.user.payment.succesful')->with(['message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success']); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return redirect()->route('dashboard.' . auth()->user()->type . '.index')->with(['message' => __('You are failed your purchase. If you paid for this, please cantact us'), 'type' => 'failed']); } } public static function handleSubscribePay($activeSub_id) { $user = Auth::user(); $settings = Setting::getCache(); $activeSub = YokassaSubscription::where('id', '=', $activeSub_id)->first(); $plan_id = $activeSub->plan_id; $plan = Plan::where('id', $plan_id)->first(); $payment_method_id = $activeSub->payment_method_id; $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); if ($gateway->mode == 'sandbox') { $shop_id = $gateway->sandbox_client_id; $key = $gateway->sandbox_client_secret; } else { $shop_id = $gateway->live_client_id; $key = $gateway->live_client_secret; } $taxValue = taxToVal($plan->price, $gateway->tax); $currency = Currency::where('id', $gateway->currency)->first()->code; $total = $plan->price + $taxValue; if ($activeSub->subscription_status == 'yokassa_approved') { $user = User::where('id', '=', $activeSub->user_id)->first(); self::creditIncreaseSubscribePlan($user, $plan); $activeSub->next_pay_at = Carbon::now()->addMonth(); $activeSub->save(); return 'success'; } else { $client = new Client; $client->setAuth($shop_id, $key); $payment = $client->createPayment( [ 'amount' => [ 'value' => $total, 'currency' => $currency, ], 'capture' => true, 'payment_method_id' => $payment_method_id, 'description' => 'Auto payment', ], uniqid('', true) ); if ($payment->paid == true) { $payment = new UserOrder; $payment->order_id = Str::random(12); $payment->plan_id = $plan->id; $payment->user_id = $user->id; $payment->payment_type = 'Credit, Debit Card'; $payment->price = $total; $payment->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $gateway->tax; $payment->tax_value = $taxValue; $payment->save(); $user = User::where('id', '=', $activeSub->user_id)->first(); self::creditIncreaseSubscribePlan($user, $plan); \App\Models\Usage::getSingle()->updateSalesCount($total); $activeSub->next_pay_at = Carbon::now()->addMonth(); $activeSub->save(); return 'success'; } else { $activeSub->payment_method_id = ''; $activeSub->subscription_status = ''; $activeSub->save(); return 'false'; } } } public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $newDiscountedPrice = $plan->price; $currency = Currency::where('id', $gateway->currency)->first()->code; if ($gateway->mode == 'sandbox') { $shop_id = $gateway->sandbox_client_id; $key = $gateway->sandbox_client_secret; } else { $shop_id = $gateway->live_client_id; $key = $gateway->live_client_secret; } $coupon = checkCouponInRequest(); $taxRate = $gateway->tax; $taxValue = taxToVal($newDiscountedPrice, $taxRate); if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } if ($taxValue > 0) { $newDiscountedPrice += $taxValue; } $client = new Client; $client->setAuth($shop_id, $key); $payment = $client->createPayment( [ 'amount' => [ 'value' => $newDiscountedPrice, 'currency' => $currency, ], 'confirmation' => [ 'type' => 'embedded', ], 'capture' => true, 'description' => 'Order No. 1', ], uniqid('', true) ); $confirmation_token = $payment->confirmation->confirmation_token; $payment_id = $payment->id; return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'payment_id', 'confirmation_token', 'taxValue', 'taxRate', 'gateway', 'currency')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $couponID = $request->input('couponID', null); $paymentId = $request->input('paymentID', null); $settings = Setting::getCache(); $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); if ($gateway->mode == 'sandbox') { $shop_id = $gateway->sandbox_client_id; $key = $gateway->sandbox_client_secret; } else { $shop_id = $gateway->live_client_id; $key = $gateway->live_client_secret; } $user = Auth::user(); try { DB::beginTransaction(); $client = new Client; $client->setAuth($shop_id, $key); $payment = $client->getPaymentInfo($paymentId); if ($payment->paid == true) { $plan = Plan::where('id', $planID)->first(); $total = $plan->price; $taxValue = taxToVal($plan->price, $gateway->tax); if ($couponID) { $coupon = Coupon::where('code', $couponID)->first(); if ($coupon) { $coupon->usersUsed()->attach(auth()->user()->id); $couponID = $coupon->discount; $total -= ($plan->price * ($coupon->discount / 100)); if ($total != floor($total)) { $total = number_format($total, 2); } } } $total += $taxValue; // new order $payment = new UserOrder; $payment->order_id = 'YPO-' . strtoupper(Str::random(13)); $payment->plan_id = $plan->id; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $total; $payment->affiliate_earnings = ($total * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $gateway->tax; $payment->tax_value = $taxValue; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Token Pack')); EmailPaymentConfirmation::create($user, $plan)->send(); DB::commit(); \App\Models\Usage::getSingle()->updateSalesCount($total); return redirect()->route('dashboard.user.payment.succesful')->with(['message' => __('Thank you for your purchase. Enjoy your remaining words and images.'), 'type' => 'success']); } return redirect()->route('dashboard.' . auth()->user()->type . '.index')->with(['message' => __('You are failed your purchase. If you paid for this, please cantact us'), 'type' => 'failed']); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> prepaidCheckout(): ' . $ex->getMessage()); return redirect()->route('dashboard.' . auth()->user()->type . '.index')->with(['message' => __('You are failed your purchase. If you paid for this, please cantact us'), 'type' => 'failed']); } } public static function getSubscriptionDaysLeft() { $userId = Auth::user()->id; $activeSub = YokassaSubscription::where([['subscription_status', '=', 'active'], ['user_id', '=', $userId]])->orWhere([['subscription_status', '=', 'yokassa_approved'], ['user_id', '=', $userId]])->first(); if ($activeSub != null) { return \Carbon\Carbon::now()->diffInDays($activeSub->next_pay_at); } return null; } public static function subscribeCancel($internalUser = null): RedirectResponse { $user = $internalUser ?? Auth::user(); $activeSub = YokassaSubscription::where([['subscription_status', '=', 'active'], ['user_id', '=', $user->id]])->orWhere([['subscription_status', '=', 'yokassa_approved'], ['user_id', '=', $user->id]])->first(); if ($activeSub !== null) { $plan = Plan::findOrFail($activeSub->plan_id); $activeSub->update([ 'subscription_status' => 'cancelled', 'next_pay_at' => Carbon::now(), ]); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); if ($internalUser != null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function getSubscriptionRenewDate() { $userId = Auth::user()->id; $activeSub = YokassaSubscription::where([['subscription_status', '=', 'active'], ['user_id', '=', $userId]])->orWhere([['subscription_status', '=', 'yokassa_approved'], ['user_id', '=', $userId]])->first(); if ($activeSub != null) { return \Carbon\Carbon::createFromTimeStamp($activeSub->next_pay_at)->format('F jS, Y'); } return null; } public static function getSubscriptionStatus($user_id = null): ?bool { $userId = $user_id ?? Auth::user()->id; $activeSub = YokassaSubscription::where([['subscription_status', '=', 'active'], ['user_id', '=', $userId]])->orWhere([['subscription_status', '=', 'yokassa_approved'], ['user_id', '=', $userId]])->first(); if ($activeSub) { // when lifetime if ($activeSub->subscription_status === 'yokassa_approved') { // TODO: we can renew from here or from command return true; } if ($activeSub['subscription_status'] === 'active') { return true; } $activeSub->subscription_status = 'cancelled'; $activeSub->next_pay_at = \Carbon\Carbon::now(); $activeSub->save(); return false; } return null; } public static function checkIfTrial() { $userId = Auth::user()->id; $activeSub = YokassaSubscription::where([['subscription_status', '=', 'active'], ['user_id', '=', $userId]])->orWhere([['subscription_status', '=', 'yokassa_approved'], ['user_id', '=', $userId]])->first(); if ($activeSub != null) { return false; } return false; } public static function cancelSubscribedPlan($subscription, $planId) { $user = Auth::user(); if ($subscription != null) { $plan = Plan::where('id', $planId)->first(); $subscription->subscription_status = 'cancelled'; $subscription->next_pay_at = Carbon::now(); $subscription->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } return false; } // webhook functions public function verifyIncomingJson(Request $request) { try { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->first(); $webhook_event = $request->getContent(); if ($webhook_event == null) { return false; } if (json_validate($webhook_event) == false) { return false; } Log::info('+++++++++++++'); Log::info($request); $payload = $request->getContent(); $yokassa_ip_array = [ '185.71.76.0/27', '185.71.77.0/27', '77.75.153.0/25', '77.75.156.11', '77.75.156.35', '77.75.154.128/25', '2a02:5180::/32', ]; $ip_address = $request->ip(); if (in_array($ip_address, $yokassa_ip_array)) { return $payload; } else { return false; } } catch (Exception $th) { Log::error('(Webhooks) Yokassa::verifyIncomingJson(): ' . $th->getMessage()); } return false; } public function handleWebhook(Request $request) { // Log::info($request->getContent()); // $verified = $request->getContent(); $verified = self::verifyIncomingJson($request); if ($verified != null) { // Retrieve the JSON payload $payload = $verified; // Fire the event with the payload event(new YokassaWebhookEvent($payload)); return response()->json(['success' => true]); } else { // Incoming json is NOT verified abort(404); } } public static function gatewayDefinitionArray(): array { return [ 'code' => 'yokassa', 'title' => 'Yokassa', 'link' => 'https://yokassa.ru/', 'active' => 0, 'available' => 1, 'img' => '/assets/img/payments/yokassa.svg', 'whiteLogo' => 0, 'mode' => 1, 'sandbox_client_id' => 1, 'sandbox_client_secret' => 1, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 1, 'live_app_id' => 0, 'currency' => 1, 'currency_locale' => 0, 'notify_url' => 0, 'base_url' => 0, 'sandbox_url' => 0, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } TwoCheckoutService.php000064400000000536152667044740011060 0ustar00with(['message' => __('Please enable coingate'), 'type' => 'error']); } $plans = Plan::query()->where('active', 1)->get(); foreach ($plans as $plan) { self::saveProduct($plan); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { $paddle = self::client(); $stripe_product_id = null; try { $product = $paddle->products->create(new CreateProduct( name: $plan['name'], taxCategory: TaxCategory::Standard(), description: $plan['name'], imageUrl: null, customData: new CustomData(['plan_id' => $plan['id']]), )); $product = self::objectToArrayHaveId($product); $stripe_product_id = data_get($product, 'id'); } catch (ProductApiError|ApiError|MalformedResponse $e) { return back()->with(['message' => $e->getMessage(), 'type' => 'error']); } try { $trialPeriod = new Undefined; if ($plan->trial_days and $plan->type != 'prepaid') { $trialPeriod = new TimePeriod(Interval::Day(), $plan->trial_days); } $billingCycle = null; if ($plan->type != 'prepaid') { $billingCycle = $plan->frequency == FrequencyEnum::MONTHLY->value ? Interval::Month() : Interval::Year(); $billingCycle = new TimePeriod($billingCycle, 1); } $price = $paddle->prices->create(new CreatePrice( description: 'Bear Hug', productId: $stripe_product_id, unitPrice: new Money($plan['price'] * 100, CurrencyCode::USD()), unitPriceOverrides: [], trialPeriod: $trialPeriod, billingCycle: $billingCycle, quantity: new PriceQuantity(1, 1), customData: new CustomData([ 'product' => json_encode($product), ]), )); $price = self::objectToArrayHaveId($price); $gateProduct = GatewayProducts::query() ->firstOrCreate([ 'plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE, 'gateway_title' => self::$GATEWAY_NAME, ]); $gateProduct->update([ 'product_id' => $product['id'], 'price_id' => $price['id'], 'payload' => json_encode($price), ]); } catch (PriceApiError|ApiError|MalformedResponse $e) { return back()->with(['message' => $e->getMessage(), 'type' => 'error']); } } public static function subscribe($plan) { $gateProduct = GatewayProducts::query() ->where('gateway_code', self::$GATEWAY_CODE) ->where('plan_id', $plan->getAttribute('id')) ->orderBy('id', 'desc') ->whereNotNull('price_id') ->value('price_id'); if ($gateProduct == null) { return back()->with(['message' => __('Please enable paddle'), 'type' => 'error']); } $user = auth()->user() ?: User::query()->first(); $gateway = self::geteway(); if ($gateway == null) { return back()->with(['message' => __('Please enable paddle'), 'type' => 'error']); } $token = $gateway->getAttribute('live_client_id'); if ($gateway->getAttribute('mode') == 'sandbox') { $token = $gateway->getAttribute('sandbox_client_id'); } $paddle = self::client(); $orderId = Str::random(10); UserOrder::query() ->create([ 'order_id' => $orderId, 'plan_id' => $plan->id, 'user_id' => $user->id, 'payment_type' => self::$GATEWAY_CODE, 'price' => $plan->price, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'tax_value' => 0, 'type' => $plan->type == 'prepaid' ? 'token-pack' : 'subscription', 'payload' => [], ]); try { if (($customerId = $user->getAttribute('stripe_id')) && ($addressId = $user->getAttribute('address_id'))) { return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'customerId', 'orderId', 'token', 'gateProduct', 'gateway')); } $findCustomer = $paddle ->customers ->list(new ListCustomers( emails: [$user->getAttribute('email')] )); if ($findCustomer && $findCustomer->valid()) { $customer = $findCustomer->current(); } else { $customer = $paddle ->customers ->create(new CreateCustomer( email: $user->getAttribute('email') ?: 'test@test.com', name: $user->getAttribute('name') ?: 'Test User', customData: new CustomData(['user_id' => $user->getAttribute('id')]), locale: 'en', )); } $customer = self::objectToArrayHaveId($customer); $customerId = data_get($customer, 'id'); $user->update([ 'stripe_id' => $customerId, ]); $address = $paddle->addresses->create($customerId, new CreateAddress( countryCode: CountryCode::US(), description: 'Home', firstLine: 'no', secondLine: '', city: '', postalCode: '23232', region: 'US', customData: new CustomData(['user_id' => $user->getAttribute('id'), 'customer_id' => $customerId]), )); $address = self::objectToArrayHaveId($address); $customer['address'] = $address; $user->update([ 'address_id' => $address['id'], 'customer_payload' => $customer, ]); $orderId = Str::random(10); return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'customerId', 'orderId', 'token', 'gateProduct', 'gateway')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { try { $checkoutData = $request->all(); if ($checkoutData == null) { return back()->with(['message' => __('Please error'), 'type' => 'error']); } $planId = $checkoutData['planID']; $plan = Plan::query()->where('id', $planId)->first(); $data = json_decode($checkoutData['checkoutData'], true); $customerId = data_get($data, 'data.customer.id'); $paddle = self::client(); $subscriptions = $paddle->subscriptions->list(new ListSubscriptions( customerIds: [$customerId] )); $current = $subscriptions->current(); $currentArray = json_decode(json_encode($current), true); $price_id_product = data_get($data, 'data.items.0.price_id'); $subscription = new Subscriptions; $subscription->user_id = auth()->id(); $subscription->name = $planId; $subscription->stripe_id = $currentArray['id']; $subscription->stripe_status = $plan->trial_days != 0 ? 'trialing' : 'active'; $subscription->stripe_price = $price_id_product; $subscription->quantity = 1; $subscription->trial_ends_at = null; $subscription->ends_at = $plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value ? Carbon::now()->addMonths(1) : Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->plan_id = $plan->id; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->save(); $orderId = $request->input('orderID', null); if ($orderId) { $order = UserOrder::query() ->where('order_id', $orderId) ->where('payment_type', self::$GATEWAY_CODE) ->first(); if ($order) { /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->plan_id)->first(); /** * @var User $user */ $user = User::query()->where('id', $order->user_id)->first(); self::creditIncreaseSubscribePlan($user, $plan); $order->update(['status' => 'PAID']); Usage::getSingle()->updateSalesCount($plan->price); } } return to_route('dashboard.user.payment.subscription') ->with([ 'message' => __('Subscription created successfully'), 'type' => 'success', ]); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribeCheckout(): ' . $ex->getMessage()); return to_route('dashboard.user.payment.subscription')->with(['message' => $ex->getMessage(), 'type' => 'error']); } return true; // TODO: Implement subscribeCheckout() method. } public static function prepaidCheckout(Request $request, $referral = null) { try { $checkoutData = $request->all(); if ($checkoutData == null) { return back()->with(['message' => __('Please error'), 'type' => 'error']); } $planId = $checkoutData['planID']; $plan = Plan::query()->where('id', $planId)->first(); $data = json_decode($checkoutData['checkoutData'], true); $customerId = data_get($data, 'data.customer.id'); $paddle = self::client(); $subscriptions = $paddle->subscriptions->list(new ListSubscriptions( customerIds: [$customerId] )); $current = $subscriptions->current(); // $currentArray = json_decode(json_encode($current), true); // $price_id_product = data_get($data, 'data.items.0.price_id'); // // $subscription = new Subscriptions(); // $subscription->user_id = auth()->id(); // $subscription->name = $planId; // $subscription->stripe_id = $currentArray['id']; // $subscription->stripe_status = $plan->trial_days != 0 ? "trialing" : "active"; // $subscription->stripe_price = $price_id_product; // $subscription->quantity = 1; // $subscription->trial_ends_at = null; // $subscription->ends_at = $plan->frequency == 'lifetime_monthly' ? Carbon::now()->addMonths(1) : Carbon::now()->addYears(1); // $subscription->auto_renewal = 1; // $subscription->plan_id = $plan->id; // $subscription->paid_with = self::$GATEWAY_CODE; // $subscription->save(); $orderId = $request->input('orderID', null); if ($orderId) { $order = UserOrder::query() ->where('order_id', $orderId) ->where('payment_type', self::$GATEWAY_CODE) ->first(); if ($order) { /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->plan_id)->first(); /** * @var User $user */ $user = User::query()->where('id', $order->user_id)->first(); self::creditIncreaseSubscribePlan($user, $plan); $order->update(['status' => 'PAID']); Usage::getSingle()->updateSalesCount($plan->price); } } return to_route('dashboard.user.payment.subscription') ->with([ 'message' => __('Payment successfully'), 'type' => 'success', ]); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribeCheckout(): ' . $ex->getMessage()); return to_route('dashboard.user.payment.subscription')->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function prepaid($plan) { $gateProduct = GatewayProducts::query() ->where('gateway_code', self::$GATEWAY_CODE) ->where('plan_id', $plan->getAttribute('id')) ->orderBy('id', 'desc') ->whereNotNull('price_id') ->value('price_id'); if ($gateProduct == null) { return back()->with(['message' => __('Please enable paddle'), 'type' => 'error']); } $user = auth()->user() ?: User::query()->first(); $gateway = self::geteway(); if ($gateway == null) { return back()->with(['message' => __('Please enable paddle'), 'type' => 'error']); } $token = $gateway->getAttribute('live_client_id'); if ($gateway->getAttribute('mode') == 'sandbox') { $token = $gateway->getAttribute('sandbox_client_id'); } $paddle = self::client(); $orderId = Str::random(10); UserOrder::query() ->create([ 'order_id' => $orderId, 'plan_id' => $plan->id, 'user_id' => $user->id, 'payment_type' => self::$GATEWAY_CODE, 'price' => $plan->price, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'tax_value' => 0, 'type' => $plan->type == 'prepaid' ? 'token-pack' : 'subscription', 'payload' => [], ]); try { if (($customerId = $user->getAttribute('stripe_id')) && ($addressId = $user->getAttribute('address_id'))) { return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'customerId', 'orderId', 'token', 'gateProduct')); } $findCustomer = $paddle ->customers ->list(new ListCustomers( emails: [$user->getAttribute('email')] )); if ($findCustomer && $findCustomer->current()) { $customer = $findCustomer->current(); } else { $customer = $paddle ->customers ->create(new CreateCustomer( email: $user->getAttribute('email') ?: 'test@test.com', name: $user->getAttribute('name') ?: 'Test User', customData: new CustomData(['user_id' => $user->getAttribute('id')]), locale: 'en', )); } $customer = self::objectToArrayHaveId($customer); $customerId = data_get($customer, 'id'); $user->update([ 'stripe_id' => $customerId, ]); $address = $paddle->addresses->create($customerId, new CreateAddress( countryCode: CountryCode::US(), description: 'Home', firstLine: 'no', secondLine: '', city: '', postalCode: '23232', region: 'US', customData: new CustomData(['user_id' => $user->getAttribute('id'), 'customer_id' => $customerId]), )); $address = self::objectToArrayHaveId($address); $customer['address'] = $address; $user->update([ 'address_id' => $address['id'], 'customer_payload' => $customer, ]); $orderId = Str::random(10); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'customerId', 'orderId', 'token', 'gateProduct', 'gateway')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribeCancel(?User $internalUser = null) { $user = Auth::user() ?: $internalUser; $userId = $user->getAttribute('id'); // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if (! $activeSub) { return back()->with(['message' => __('Error!'), 'type' => 'error']); } /** * @var Plan $plan */ $plan = Plan::query() ->where('id', $activeSub->getAttribute('plan_id')) ->first(); $paddle = self::client(); $data = $paddle->subscriptions->cancel( $activeSub->getAttribute('stripe_id'), new CancelSubscription(SubscriptionEffectiveFrom::Immediately()) ); $status = data_get($data, 'data.status'); if ($status == 'canceled') { $activeSub->stripe_status = $status; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled successfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function cancelSubscribedPlan($subscription, $planId) { $paddle = self::client(); $user = Auth::user(); $check = $subscription instanceof Subscriptions; if (! $check) { $subscription = Subscriptions::where('id', $subscription)->first(); } if ($subscription == null) { return back()->with(['message' => __('Subscription not found'), 'type' => 'error']); } try { $paddle->subscriptions->cancel( $subscription->getAttribute('stripe_id'), new CancelSubscription(SubscriptionEffectiveFrom::Immediately()) ); $subscription->update(['stripe_status' => 'canceled']); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> cancelSubscribedPlan(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function checkIfTrial() { // TODO: Implement checkIfTrial() method. } public static function getSubscriptionRenewDate() { $user = Auth::user() ?: User::query()->first(); $subscription = getCurrentActiveSubscription($user->getAttribute('id')); $next_delivery_date = null; if ($subscription) { $id = $subscription->getAttribute('stripe_id'); $request = self::client()->subscriptions->get($id); $next_delivery_date = data_get($request, 'nextBilledAt'); } if ($next_delivery_date) { return Carbon::create($next_delivery_date)->format('F jS, Y'); } return false; } public static function getSubscriptionStatus($incomingUserId = null) { $user = null; if ($incomingUserId) { $user = User::query()->where('id', $incomingUserId)->first(); } if (Auth::check() && $incomingUserId == null) { $user = Auth::user(); } if (! $user) { return false; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_id'); try { $request = self::client()->subscriptions->get($id); $status = data_get($request, 'status'); if ((string) $status == 'active') { return true; } else { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => Carbon::now(), ]); } return false; } } catch (Exception $th) { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => Carbon::now(), ]); } return false; } } return false; } public static function getSubscriptionDaysLeft() { $user = null; if (Auth::check()) { $user = Auth::user(); } if (! $user) { return; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_id'); try { $request = self::client()->subscriptions->get($id); $next_delivery_date = data_get($request, 'nextBilledAt'); if ($next_delivery_date) { $next_delivery_date = Carbon::create($next_delivery_date); return $next_delivery_date->diffInDays(Carbon::now()); } } catch (Exception $th) { return false; } } } public static function handleWebhook(Request $request) {} public static function client() { $gateway = self::geteway(); if (! $gateway) { return; } $apiKey = $gateway->getAttribute('live_client_secret'); $environment = Environment::PRODUCTION; if ($gateway->getAttribute('mode') == 'sandbox') { $apiKey = $gateway->getAttribute('sandbox_client_secret'); $environment = Environment::SANDBOX; } return new Client($apiKey, options: new Options($environment)); } public static function geteway(): Model|Builder|null { if (self::$gateway) { return self::$gateway; } self::$gateway = Gateways::where('code', self::$GATEWAY_CODE)->first(); return self::$gateway; } public static function objectToArrayHaveId($request) { if (is_object($request)) { if (property_exists($request, 'id')) { $request = json_decode(json_encode($request), true); } } return $request; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'paddle', 'title' => 'Paddle', 'link' => 'https://paddle.com/', 'active' => 0, // if user activated this gateway - dynamically filled in main page 'available' => 1, // if gateway is available to use 'img' => custom_theme_url('/assets/img/payments/paddle.svg'), 'whiteLogo' => 0, // if gateway logo is white 'mode' => 1, // Option in settings - Automatically set according to the "Development" mode. "Development" ? sandbox : live (PAYPAL - 1) 'sandbox_client_id' => 1, // Option in settings 0-Hidden 1-Visible 'sandbox_client_secret' => 1, // Option in settings 'sandbox_app_id' => 0, // Option in settings 'live_client_id' => 1, // Option in settings 'live_client_secret' => 1, // Option in settings 'live_app_id' => 0, // Option in settings 'currency' => 0, // Option in settings 'currency_locale' => 0, // Option in settings 'base_url' => 0, // Option in settings 'sandbox_url' => 0, // Option in settings 'locale' => 0, // Option in settings 'validate_ssl' => 0, // Option in settings 'logger' => 0, // Option in settings 'notify_url' => 0, // Gateway notification url at our side 'webhook_secret' => 0, // Option in settings 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } Contracts/CreditUpdater.php000064400000004061152667044740011774 0ustar00getAttribute('is_team_plan') ?? false; if ($isTeamPlan) { $team = app(TeamController::class)->getTeam($user); } $modelsCredit = $plan->getAttribute('ai_models'); foreach ($modelsCredit as $modelsGroup) { foreach ($modelsGroup as $model => $credit) { $driver = $isTeamPlan ? Entity::driver(EntityEnum::fromSlug($model))->forTeam($team) : Entity::driver(EntityEnum::fromSlug($model))->forUser($user); if ($plan->getAttribute('reset_credits_on_renewal')) { $driver->setCredit($credit['credit']); } else { $driver->increaseCredit($credit['credit']); } $driver->setAsUnlimited($credit['isUnlimited']); } } } /** * @throws Exception */ public static function creditDecreaseCancelPlan(User $user, Plan $plan): void { $team = null; $isTeamPlan = $plan->getAttribute('is_team_plan') ?? false; if ($isTeamPlan) { $team = app(TeamController::class)->getTeam($user); } if ((bool) setting('soft_plan_cancellation', false)) { return; } $modelsCredit = $plan->getAttribute('ai_models'); foreach ($modelsCredit as $modelsGroup) { foreach ($modelsGroup as $model => $credit) { $driver = $isTeamPlan ? Entity::driver(EntityEnum::fromSlug($model))->forTeam($team) : Entity::driver(EntityEnum::fromSlug($model))->forUser($user); $driver->setAsUnlimited(false); $driver->decreaseCredit($credit['credit']); } } } } Contracts/ProductInterface.php000064400000000130152667044740012467 0ustar00first(); if ($gateway == null) { return back()->with(['message' => __('Please enable iyzico'), 'type' => 'error']); } $plans = Plan::where('active', 1)->get(); foreach ($plans as $plan) { self::saveProduct($plan); } // Create webhook of iyzico later // $tmp = self::createWebhook(); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public function planFrequencyControl(?Plan $plan): bool { return $plan->price > 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value; } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $iyzipayActions = self::retrieveGatewaySettings(); try { DB::beginTransaction(); $product = null; $oldProductId = null; // check if product exists $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); // if Subscription price and its not lifetime subscription if ($plan->price > 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { if ($product == null) { $prd = json_decode(json_encode([ 'name' => $plan->name, ])); $newProduct = $iyzipayActions->createSubscriptionProduct($prd); if ($newProduct->getReferenceCode() != null) { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->plan_name = $plan->name; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_CODE; $product->product_id = $newProduct->getReferenceCode(); $product->save(); } else { Log::error('IyzicoService::saveProduct() - Product could not be created. Product : ' . json_encode($newProduct)); } } else { $prd = json_decode(json_encode([ 'productReferenceCode' => $product->product_id, ])); $checkProduct = $iyzipayActions->retrieveSubscriptionProduct($prd); if ($checkProduct->getReferenceCode() == null) { if ($product->product_id != null) { // Product has been created before $oldProductId = $product->product_id; } $prd = json_decode(json_encode([ 'name' => $plan->name, ])); $newProduct = $iyzipayActions->createSubscriptionProduct($prd); if ($newProduct->getReferenceCode() != null) { $product->product_id = $newProduct->getReferenceCode(); $product->plan_name = $plan->name; $product->save(); } else { Log::error('IyzicoService::saveProduct() - Product could not be created. Product : ' . json_encode($newProduct)); } } } } else { if ($product == null) { $product = new GatewayProducts; $product->plan_id = $plan->id; $product->plan_name = $plan->name; $product->gateway_code = self::$GATEWAY_CODE; $product->gateway_title = self::$GATEWAY_CODE; } // one-Time price $product->product_id = 'Not Needed'; $product->save(); } // check if price exists $total = $plan->price + taxToVal($plan->price, $gateway->tax); if ($product->price_id != null) { // price_id exists so we dont need to create plans // if Subscription price and its not lifetime subscription if ($plan->price > 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { // check if price exists in iyzico $prd = json_decode(json_encode([ 'pricingPlanReferenceCode' => $product->price_id, ])); $checkPrice = $iyzipayActions->retrieveSubscriptionPricingPlan($prd); if ($checkPrice->getReferenceCode() == null) { $oldPricingPlanId = $product->price_id; // create new plan with new values $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'MONTHLY' : 'YEARLY'; if ($plan->trial_days != 'undefined') { $trials = $plan->trial_days ?? 0; } else { $trials = 0; } $pricingPlan = json_decode(json_encode([ 'paymentInterval' => $interval, 'paymentIntervalCount' => 1, 'paymentType' => 'RECURRING', 'trialPeriodDays' => $trials, 'productReferenceCode' => $product->product_id, 'price' => BigDecimal::of($total)->toScale(2), 'name' => $product->plan_name, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscriptionPricingPlan = $iyzipayActions->createSubscriptionPricingPlan($pricingPlan); if ($subscriptionPricingPlan->getReferenceCode()) { $product->price_id = $subscriptionPricingPlan->getReferenceCode(); $product->save(); $history = new OldGatewayProducts; $history->plan_id = $plan->id; $history->plan_name = $plan->name; $history->gateway_code = self::$GATEWAY_CODE; $history->product_id = $product->product_id; $history->old_product_id = $oldProductId; $history->old_price_id = $oldPricingPlanId; $history->new_price_id = $subscriptionPricingPlan->getReferenceCode(); $history->status = 'check'; $history->save(); self::updateUserData(); } else { Log::error('IyzicoService::saveProduct() - Pricing Plan could not be created. Pricing Plan : id: ' . $plan->id . ', name: ' . $plan->name . json_encode($subscriptionPricingPlan)); } } } else { // One-Time price || iyzico handles one time prices with payments, so we do not need to set anything for one-time payments. $product->price_id = 'Not Needed'; $product->save(); } } else { // price_id is null so we need to create plans // if Subscription price and its not lifetime subscription if ($plan->price > 0 && $plan->type == TypeEnum::SUBSCRIPTION->value && $plan->frequency !== FrequencyEnum::LIFETIME_MONTHLY->value && $plan->frequency !== FrequencyEnum::LIFETIME_YEARLY->value) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'MONTHLY' : 'YEARLY'; if ($plan->trial_days !== 'undefined') { $trials = $plan->trial_days ?? 0; } else { $trials = 0; } $pricingPlan = json_decode(json_encode([ 'paymentInterval' => $interval, 'paymentIntervalCount' => 1, 'paymentType' => 'RECURRING', 'trialPeriodDays' => $trials, 'productReferenceCode' => $product->product_id, 'price' => BigDecimal::of($total)->toScale(2), // number_format($total, 2), 'name' => $product->plan_name, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscriptionPricingPlan = $iyzipayActions->createSubscriptionPricingPlan($pricingPlan); if ($subscriptionPricingPlan->getReferenceCode() != null) { $product->price_id = $subscriptionPricingPlan->getReferenceCode(); $product->save(); } else { Log::error('IyzicoService::saveProduct() - Pricing Plan could not be created. Pricing Plan : id: ' . $plan->id . ', name: ' . $plan->name . json_encode($subscriptionPricingPlan)); } } else { // One-Time price || iyzico handles one time prices with orders, so we do not need to set anything for one-time payments. $product->price_id = 'Not Needed'; $product->save(); } } DB::commit(); } catch (Exception $ex) { DB::rollBack(); Log::error("IyzicoService::saveProduct()\n" . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribe($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); try { $exception = null; $coupon = checkCouponInRequest(); // if there a coupon in request it will return the coupin instanse $newDiscountedPrice = $plan->price; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $currency = Currency::where('id', $gateway->currency)->first()->code; if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); $newDiscountedPriceCents = (int) (((float) $newDiscountedPrice) * 100); if ($newDiscountedPrice !== floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } $newDiscountedPrice += $taxValue; $iyzicoPriceId = self::getIyzicoPriceId($plan->id); if (is_null($iyzicoPriceId)) { $exception = __('Product ID is not set! Please save Membership Plan again.'); return back()->with(['message' => $exception, 'type' => 'error']); } return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE . '.pre', compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'exception', 'currency', 'iyzicoPriceId')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } } public static function subscribeCheckout(Request $request, $referral = null) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $iyzipayActions = self::retrieveGatewaySettings(); $user = auth()->user(); $planID = $request->input('planID', null); $couponID = $request->input('couponID', null); $plan = Plan::find($planID) ?? abort(404); $newDiscountedPrice = $plan->price + taxToVal($plan->price, $gateway->tax); $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $exception = null; $checkoutform = null; try { if (is_null($request->all())) { return back()->with(['message' => __('Please fill all fields'), 'type' => 'error']); } $rules = [ 'name' => 'required', 'surname' => 'required', 'identityNumber' => 'required', 'email' => 'required|email', 'gsmNumber' => 'required', 'registrationAddress' => 'required', 'city' => 'required', 'country' => 'required', 'zipCode' => 'required', 'iyzicoPriceId' => 'required', 'ip' => 'required', ]; $messages = [ 'required' => __('Please fill all fields'), 'email' => __('Invalid email format'), ]; $validator = Validator::make($request->all(), $rules, $messages); if ($validator->fails()) { return back()->withErrors($validator)->withInput(); } if ($plan->type === TypeEnum::SUBSCRIPTION->value && ($plan->frequency === FrequencyEnum::LIFETIME_MONTHLY->value || $plan->frequency === FrequencyEnum::LIFETIME_YEARLY->value)) { // create a new instance of incoming $request for buyer $customerRequest = json_decode(json_encode([ 'id' => Auth::user()->id, 'planId' => $planID, 'name' => $request->name, 'surname' => $request->surname, 'identityNumber' => $request->identityNumber, 'email' => $request->email, 'gsmNumber' => $request->gsmNumber, 'registrationAddress' => $request->registrationAddress, 'city' => $request->city, 'country' => $request->country, 'zipCode' => $request->zipCode, 'ip' => $request->ip, ])); // create buyer from request data $buyer = $iyzipayActions->createBuyer($customerRequest); // create a new instance of incoming $request for address $addressRequest = json_decode(json_encode([ 'contactName' => $request->name . ' ' . $request->surname, 'address' => $request->registrationAddress, 'city' => $request->city, 'country' => $request->country, 'zipCode' => $request->zipCode, ])); // create address from request data $address = $iyzipayActions->createAddress($addressRequest); $basketItemsArray = []; if ($couponID !== null) { $coupon = checkCouponInRequest($couponID); $couponID = $coupon->discount; $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } $coupon->usersUsed()->attach(auth()->user()->id); session_start(); $_SESSION['applied_coupon'] = [ 'coupon' => $coupon, 'plan_id' => $plan->id, ]; session_write_close(); } $basketItems = [ 'basketItemId' => $plan->id, 'name' => $plan->name, 'category1' => 'Token Packs', 'itemType' => 'VIRTUAL', 'price' => $newDiscountedPrice, ]; $basketItem_0 = $iyzipayActions->createBasketItem($basketItems); // now we have everthing to create one time payment. Sum them to one request $paymentRequest = json_decode(json_encode([ 'price' => $newDiscountedPrice, 'paidPrice' => $newDiscountedPrice, 'paymentGroup' => 'PRODUCT', 'callbackUrl' => route('dashboard.user.payment.iyzico.prepaid.callback'), 'enabledInstallments' => [1, 2, 3, 6, 9], 'buyer' => $buyer, 'shippingAddress' => $address, 'billingAddress' => $address, 'basketItems' => $basketItemsArray, ])); // create checkout form for one time payment with paymentRequest $requestOneTimePayment = new \Iyzipay\Request\CreateCheckoutFormInitializeRequest; $requestOneTimePayment->setPrice($newDiscountedPrice); $requestOneTimePayment->setPaidPrice($newDiscountedPrice); $requestOneTimePayment->setCallbackUrl(route('dashboard.user.payment.iyzico.subscribe.callback')); $requestOneTimePayment->setEnabledInstallments([1, 2, 3, 6, 9]); $requestOneTimePayment->setBuyer($buyer); $requestOneTimePayment->setShippingAddress($address); $requestOneTimePayment->setBillingAddress($address); $requestOneTimePayment->setBasketItems([$basketItem_0]); $checkoutform = \Iyzipay\Model\CheckoutFormInitialize::create($requestOneTimePayment, $iyzipayActions->getConfig()); if ($checkoutform->getStatus() === 'failure') { $errorCode = $checkoutform->getErrorCode(); $errorMessage = $checkoutform->getErrorMessage(); return back()->with([ 'message' => __('Please enter valid information!') . " Error Code: $errorCode - $errorMessage", 'type' => 'error', ]); } // function did not work out for now. may be we can turn back after // $checkoutform = $iyzipayActions->createOneTimePayment($paymentRequest); // Since we can not transfer anything except token id to callback page we must use a middle step // We are going to save token id to CustomSettings table and retrieve it from callback page. $customSettings = new CustomSettings; $customSettings->key = $checkoutform->getToken(); $customSettings->value_str = strval($plan->id); $customSettings->save(); } else { // create a new instance of incoming $request for subscription customer $customerRequest = json_decode(json_encode([ // "id" => $user->id, 'name' => $request->name, 'surname' => $request->surname, 'identityNumber' => $request->identityNumber, 'email' => $request->email, 'gsmNumber' => $request->gsmNumber, 'shippingContactName' => $request->name . ' ' . $request->surname, 'shippingCity' => $request->city, 'shippingCountry' => $request->country, 'shippingAddress' => $request->registrationAddress, 'shippingZipCode' => $request->zipCode, 'billingContactName' => $request->name . ' ' . $request->surname, 'billingCity' => $request->city, 'billingCountry' => $request->country, 'billingAddress' => $request->registrationAddress, 'billingZipCode' => $request->zipCode, ])); if ($user->iyzico_id != null) { // retrieve customer from iyzico $cst = json_decode(json_encode([ 'customerReferenceCode' => $user->iyzico_id, ])); $customer = $iyzipayActions->retrieveSubscriptionCustomer($cst); if ($customer->getReferenceCode() != null) { // customer exists Log::info('iyzico customer exists'); $customerRequest->customerReferenceCode = $user->iyzico_id; $customer = $iyzipayActions->updateSubscriptionCustomer($customerRequest); } else { // customer does not exist Log::info('iyzico customer does not exist'); $customer = $iyzipayActions->createCustomer($customerRequest); } } else { // create customer from request data Log::info('iyzico customer does not exist'); $customer = $iyzipayActions->createCustomer($customerRequest); } // check if customer set if ($customer == null) { return back()->with(['message' => __('Customer could not set'), 'type' => 'error']); } $newDPriceID = $request->iyzicoPriceId; if ($couponID !== null) { $coupon = checkCouponInRequest($couponID); $couponID = $coupon->discount; $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } $coupon->usersUsed()->attach(auth()->user()->id); $prd = json_decode(json_encode([ 'name' => 'discount_' . $coupon->code . '_' . $user->id . '_' . $plan->id . '_' . time(), ])); $newProduct = $iyzipayActions->createSubscriptionProduct($prd); if ($newProduct->getReferenceCode() != null) { $interval = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'MONTHLY' : 'YEARLY'; if ($plan->trial_days != 'undefined') { $trials = $plan->trial_days ?? 0; } else { $trials = 0; } $pricingPlan = json_decode(json_encode([ 'paymentInterval' => $interval, 'paymentIntervalCount' => 1, 'paymentType' => 'RECURRING', 'trialPeriodDays' => $trials, 'productReferenceCode' => $newProduct->getReferenceCode(), 'price' => BigDecimal::of($newDiscountedPrice)->toScale(2), 'name' => $prd->name, ])); $subscriptionPricingPlan = $iyzipayActions->createSubscriptionPricingPlan($pricingPlan); if ($subscriptionPricingPlan->getReferenceCode() == null) { Log::error('IyzicoService::saveProduct() - Pricing Plan could not be created. Pricing Plan : id: ' . $plan->id . ', name: ' . $plan->name . json_encode($subscriptionPricingPlan)); } session_start(); $_SESSION['applied_coupon'] = [ 'coupon' => $coupon, 'plan_id' => $plan->id, ]; session_write_close(); $newDPriceID = $subscriptionPricingPlan->getReferenceCode(); } } $checkoutFormRequest = new \Iyzipay\Request\Subscription\SubscriptionCreateCheckoutFormRequest; $checkoutFormRequest->setConversationId($iyzipayActions->generateRandomNumber()); $checkoutFormRequest->setLocale($iyzipayActions->getLocale()); $checkoutFormRequest->setPricingPlanReferenceCode($newDPriceID); $checkoutFormRequest->setSubscriptionInitialStatus('ACTIVE'); $checkoutFormRequest->setCallbackUrl(route('dashboard.user.payment.iyzico.subscribe.callback')); $checkoutFormRequest->setCustomer($customer); $checkoutform = \Iyzipay\Model\Subscription\SubscriptionCreateCheckoutForm::create($checkoutFormRequest, $iyzipayActions->getConfig()); if ($checkoutform->getStatus() === 'failure') { $errorCode = $checkoutform->getErrorCode(); $errorMessage = $checkoutform->getErrorMessage(); return back()->with([ 'message' => __('Please enter valid information!') . " Error Code: $errorCode - $errorMessage", 'type' => 'error', ]); } Log::info('checkoutform : ' . json_encode($checkoutform)); if ($checkoutform == null) { $exception = 'Checkout form could not be created'; return back()->with(['message' => __('Please enter valid information!'), 'type' => 'error']); } // Since we can not transfer anything except token id to callback page we must use a middle step // We are going to save token id to CustomSettings table and retrieve it from callback page. $customSettings = new CustomSettings; $customSettings->key = $checkoutform->getToken(); $customSettings->value_str = strval($planID); $customSettings->save(); } } catch (Exception $th) { $exception = $th->getMessage(); // $exception = Str::before($th->getMessage(),':'); } return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE . '.pay', compact('plan', 'taxRate', 'taxValue', 'newDiscountedPrice', 'gateway', 'exception', 'currency', 'checkoutform')); } public static function iyzicoSubscribeCallback(Request $request) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $couponID = null; $plan = null; $exception = null; $settings = Setting::getCache(); $user = Auth::user(); $type = 'subscription'; // check if request has token if ($request->token == null) { return back()->with(['message' => __('Token is missing'), 'type' => 'error']); } try { DB::beginTransaction(); // get iyzipayActions class $iyzipayActions = self::retrieveGatewaySettings(); // retrieve subscription result $checkoutresultrequest = new \Iyzipay\Request\Subscription\RetrieveSubscriptionCreateCheckoutFormRequest; $checkoutresultrequest->setCheckoutFormToken(strval($request->token)); $checkoutresult = \Iyzipay\Model\Subscription\RetrieveSubscriptionCheckoutForm::retrieve($checkoutresultrequest, $iyzipayActions->getConfig()); if ($checkoutresult->getStatus() == 'success' && ($checkoutresult->getSubscriptionStatus() == 'ACTIVE' || $checkoutresult->getSubscriptionStatus() == 'active')) { // Since we could not transfer anything except token id to callback page we must use a middle step // We saved token id to CustomSettings table and retrieve it now $customSettings = CustomSettings::where('key', $request->token)->first(); if ($customSettings == null) { // if we can't get plan id, just save it with a warning. So user can check from iyzico backend. $payment = new UserOrder; $payment->order_id = $checkoutresult->getReferenceCode(); $payment->plan_id = 'Missing Plan Id. Check with token and order id.'; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = 0; $payment->affiliate_earnings = 0; $payment->status = 'Success with token:' . $request->token; $payment->country = $user->country ?? 'Unknown'; $payment->save(); return back()->with(['message' => __('Token is missing'), 'type' => 'error']); } // get plan id from CustomSettings table $planId = $customSettings->value_str; // get plan $plan = Plan::where('id', $planId)->first(); $newDiscountedPrice = $plan->price; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $newDiscountedPrice = $plan->price; session_start(); // Start the session if not already started if (isset($_SESSION['applied_coupon'])) { $appliedCouponData = $_SESSION['applied_coupon']; if ($appliedCouponData['plan_id'] == $planId) { $appliedCoupon = $appliedCouponData['coupon']; $newDiscountedPrice = $plan->price - ($plan->price * ($appliedCoupon->discount / 100)); $couponID = $appliedCoupon->discount; } unset($_SESSION['applied_coupon']); } session_write_close(); $newDiscountedPrice += $taxValue; // save checkout to orders $payment = new UserOrder; $payment->order_id = $request->token; $payment->plan_id = $plan->id; $payment->type = 'subscription'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $newDiscountedPrice; $payment->affiliate_earnings = ($newDiscountedPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $taxRate; $payment->tax_value = $taxValue; $payment->save(); // get gateway product related to plan id $product = GatewayProducts::where(['plan_id' => $planId, 'gateway_code' => self::$GATEWAY_CODE])->first(); // save subscription to database $subscription = new Subscriptions; $subscription->stripe_id = $checkoutresult->getReferenceCode(); $subscription->stripe_status = 'active'; $subscription->ends_at = $plan->trial_days != 0 ? Carbon::now()->addDays($plan->trial_days) : Carbon::now()->addDays(30); $subscription->user_id = $user->id; $subscription->name = $planId; $subscription->stripe_price = $product->price_id; $subscription->quantity = 1; $subscription->trial_ends_at = $plan->trial_days != 0 ? Carbon::now()->addDays($plan->trial_days) : null; $subscription->plan_id = $planId; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->tax_rate = $taxRate; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $newDiscountedPrice; $subscription->save(); // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $checkoutresult->getParentReferenceCode(); // $subscriptionItem->stripe_product = $product->product_id; self::creditIncreaseSubscribePlan($user, $plan); // delete custom settings since we do not need it anymore $customSettings->delete(); \App\Models\Usage::getSingle()->updateSalesCount($newDiscountedPrice); CreateActivity::for($user, __('Subscribed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); } else { // lifetime plan $checkoutRequest = [ 'token' => $request->token, ]; // retrieve one time payment result $checkoutresult = $iyzipayActions->retrieveOneTimePayment($checkoutRequest); if ($checkoutresult->getStatus() == 'success' && ($checkoutresult->getPaymentStatus() == 'SUCCESS' || $checkoutresult->getPaymentStatus() == 'success')) { $type = 'prepaid'; // Since we could not transfer anything except token id to callback page we must use a middle step // We saved token id to CustomSettings table and retrieve it now $customSettings = CustomSettings::where('key', $request->token)->first(); if ($customSettings == null) { // if we can't get plan id, just save it with a warning. So user can check from iyzico backend. $payment = new UserOrder; $payment->order_id = $checkoutresult->getPaymentId(); $payment->plan_id = 'Missing Plan Id. Check with token and order id.'; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = 0; $payment->affiliate_earnings = 0; $payment->status = 'Success with token:' . $request->token; $payment->country = $user->country ?? 'Unknown'; $payment->save(); return back()->with(['message' => __('Token is missing'), 'type' => 'error']); } $planId = $customSettings->value_str; $plan = Plan::where('id', $planId)->first(); if ($plan->type == 'subscription' && ($plan->frequency == FrequencyEnum::LIFETIME_MONTHLY->value || $plan->frequency == FrequencyEnum::LIFETIME_YEARLY->value)) { $newDiscountedPrice = $plan->price; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $newDiscountedPrice = $plan->price; session_start(); // Start the session if not already started if (isset($_SESSION['applied_coupon'])) { $appliedCouponData = $_SESSION['applied_coupon']; if ($appliedCouponData['plan_id'] == $planId) { $appliedCoupon = $appliedCouponData['coupon']; $newDiscountedPrice = $plan->price - ($plan->price * ($appliedCoupon->discount / 100)); $couponID = $appliedCoupon->discount; } unset($_SESSION['applied_coupon']); } session_write_close(); $newDiscountedPrice += $taxValue; // save checkout to orders $payment = new UserOrder; $payment->order_id = $request->token; $payment->plan_id = $plan->id; $payment->type = 'subscription'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $newDiscountedPrice; $payment->affiliate_earnings = ($newDiscountedPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $taxRate; $payment->tax_value = $taxValue; $payment->save(); // get gateway product related to plan id $product = GatewayProducts::where(['plan_id' => $planId, 'gateway_code' => self::$GATEWAY_CODE])->first(); // save subscription to database $subscription = new Subscriptions; $subscription->stripe_id = 'ILS-' . strtoupper(Str::random(13)); $subscription->ends_at = $plan->frequency == 'lifetime_monthly' ? Carbon::now()->addMonths(1) : Carbon::now()->addYears(1); $subscription->auto_renewal = 1; $subscription->stripe_status = 'iyzico_approved'; $subscription->user_id = $user->id; $subscription->name = $planId; $subscription->stripe_price = $product->price_id; $subscription->quantity = 1; $subscription->trial_ends_at = $plan->trial_days != 0 ? Carbon::now()->addDays($plan->trial_days) : null; $subscription->plan_id = $planId; $subscription->paid_with = self::$GATEWAY_CODE; $subscription->tax_rate = $taxRate; $subscription->tax_value = $taxValue; $subscription->coupon = $couponID; $subscription->total_amount = $newDiscountedPrice; $subscription->save(); // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $subscription->stripe_id; // $subscriptionItem->stripe_product = $product->product_id; // $subscriptionItem->stripe_price = $product->price_id; // $subscriptionItem->quantity = 1; // $subscriptionItem->save(); self::creditIncreaseSubscribePlan($user, $plan); // delete custom settings since we do not need it anymore $customSettings->delete(); \App\Models\Usage::getSingle()->updateSalesCount($newDiscountedPrice); CreateActivity::for($user, __('Subscribed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); } } } DB::commit(); return view('panel.user.finance.' . $type . '.' . self::$GATEWAY_CODE . '.result', compact('plan', 'type', 'gateway', 'exception', 'currency', 'checkoutresult')); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> iyzicoSubscribeCallback(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } } public static function prepaid($plan) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $newDiscountedPrice = $plan->price; $currency = Currency::where('id', $gateway->currency)->first()->code; $coupon = checkCouponInRequest(); $taxRate = $gateway->tax; $taxValue = taxToVal($newDiscountedPrice, $taxRate); if (self::getIyzicoPriceId($plan->id) === null) { $exception = 'Product ID is not set! Please save Membership Plan again.'; return back()->with(['message' => $exception, 'type' => 'error']); } try { if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice !== floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } if ($taxValue > 0) { $newDiscountedPrice += $taxValue; } return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE . '.pre', compact('plan', 'newDiscountedPrice', 'taxValue', 'taxRate', 'gateway', 'currency')); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> prepaid(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $iyzipayActions = self::retrieveGatewaySettings(); $user = auth()->user(); $planID = $request->input('planID', null); $couponID = $request->input('couponID', null); $plan = Plan::find($planID) ?? abort(404); $newDiscountedPrice = $plan->price + taxToVal($plan->price, $gateway->tax); $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $exception = null; $checkoutform = null; try { if (is_null($request->all())) { return back()->with(['message' => __('Please fill all fields'), 'type' => 'error']); } $rules = [ 'name' => 'required', 'surname' => 'required', 'identityNumber' => 'required', 'email' => 'required|email', 'gsmNumber' => 'required', 'registrationAddress' => 'required', 'city' => 'required', 'country' => 'required', 'zipCode' => 'required', 'ip' => 'required', ]; $messages = [ 'required' => __('Please fill all fields'), 'email' => __('Invalid email format'), ]; $validator = Validator::make($request->all(), $rules, $messages); if ($validator->fails()) { return back()->withErrors($validator)->withInput(); } // create a new instance of incoming $request for buyer $customerRequest = json_decode(json_encode([ 'id' => Auth::user()->id, 'planId' => $planID, 'name' => $request->name, 'surname' => $request->surname, 'identityNumber' => $request->identityNumber, 'email' => $request->email, 'gsmNumber' => $request->gsmNumber, 'registrationAddress' => $request->registrationAddress, 'city' => $request->city, 'country' => $request->country, 'zipCode' => $request->zipCode, 'ip' => $request->ip, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); // create buyer from request data $buyer = $iyzipayActions->createBuyer($customerRequest); // create a new instance of incoming $request for address $addressRequest = json_decode(json_encode([ 'contactName' => $request->name . ' ' . $request->surname, 'address' => $request->registrationAddress, 'city' => $request->city, 'country' => $request->country, 'zipCode' => $request->zipCode, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); // create address from request data $address = $iyzipayActions->createAddress($addressRequest); $basketItemsArray = []; if ($couponID !== null) { $coupon = checkCouponInRequest($couponID); $couponID = $coupon->discount; $newDiscountedPrice -= ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice !== floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } $coupon->usersUsed()->attach(auth()->user()->id); session_start(); $_SESSION['applied_coupon'] = [ 'coupon' => $coupon, 'plan_id' => $plan->id, ]; session_write_close(); } $basketItems = [ 'basketItemId' => $plan->id, 'name' => $plan->name, 'category1' => 'Token Packs', 'itemType' => 'VIRTUAL', 'price' => $newDiscountedPrice, ]; $basketItem_0 = $iyzipayActions->createBasketItem($basketItems); // now we have everthing to create one time payment. Sum them to one request $paymentRequest = json_decode(json_encode([ 'price' => $newDiscountedPrice, 'paidPrice' => $newDiscountedPrice, 'paymentGroup' => 'PRODUCT', 'callbackUrl' => route('dashboard.user.payment.iyzico.prepaid.callback'), 'enabledInstallments' => [1, 2, 3, 6, 9], 'buyer' => $buyer, 'shippingAddress' => $address, 'billingAddress' => $address, 'basketItems' => $basketItemsArray, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); // create checkout form for one time payment with paymentRequest $requestOneTimePayment = new \Iyzipay\Request\CreateCheckoutFormInitializeRequest; $requestOneTimePayment->setPrice($newDiscountedPrice); $requestOneTimePayment->setPaidPrice($newDiscountedPrice); $requestOneTimePayment->setCallbackUrl(route('dashboard.user.payment.iyzico.prepaid.callback')); $requestOneTimePayment->setEnabledInstallments([1, 2, 3, 6, 9]); $requestOneTimePayment->setBuyer($buyer); $requestOneTimePayment->setShippingAddress($address); $requestOneTimePayment->setBillingAddress($address); $requestOneTimePayment->setBasketItems([$basketItem_0]); $checkoutform = \Iyzipay\Model\CheckoutFormInitialize::create($requestOneTimePayment, $iyzipayActions->getConfig()); if ($checkoutform->getStatus() === 'failure') { $errorCode = $checkoutform->getErrorCode(); $errorMessage = $checkoutform->getErrorMessage(); return back()->with([ 'message' => __('Please enter valid information!') . " Error Code: $errorCode - $errorMessage", 'type' => 'error', ]); } // function did not work out for now. may be we can turn back after // $checkoutform = $iyzipayActions->createOneTimePayment($paymentRequest); // Since we can not transfer anything except token id to callback page we must use a middle step // We are going to save token id to CustomSettings table and retrieve it from callback page. $customSettings = new CustomSettings; $customSettings->key = $checkoutform->getToken(); $customSettings->value_str = strval($plan->id); $customSettings->save(); } catch (Exception $th) { $exception = Str::before($th->getMessage(), ':'); } return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE . '.pay', compact('plan', 'taxRate', 'taxValue', 'newDiscountedPrice', 'gateway', 'exception', 'currency', 'checkoutform')); } public static function iyzicoPrepaidCallback(Request $request) { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $couponID = null; $plan = null; $exception = null; $settings = Setting::getCache(); $user = Auth::user(); if ($request->token === null) { return back()->with(['message' => __('Token is missing'), 'type' => 'error']); } try { DB::beginTransaction(); $iyzipayActions = self::retrieveGatewaySettings(); // create request of one time payment result $checkoutRequest = [ 'token' => $request->token, ]; // retrieve one time payment result $checkoutresult = $iyzipayActions->retrieveOneTimePayment($checkoutRequest); if ($checkoutresult->getStatus() === 'success' && ($checkoutresult->getPaymentStatus() === 'SUCCESS' || $checkoutresult->getPaymentStatus() === 'success')) { // Since we could not transfer anything except token id to callback page we must use a middle step // We saved token id to CustomSettings table and retrieve it now $customSettings = CustomSettings::where('key', $request->token)->first(); if ($customSettings === null) { // if we can't get plan id, just save it with a warning. So user can check from iyzico backend. $payment = new UserOrder; $payment->order_id = $checkoutresult->getPaymentId(); $payment->plan_id = 'Missing Plan Id. Check with token and order id.'; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = 0; $payment->affiliate_earnings = 0; $payment->status = 'Success with token:' . $request->token; $payment->country = $user->country ?? 'Unknown'; $payment->save(); return back()->with(['message' => __('Token is missing'), 'type' => 'error']); } // get plan id from CustomSettings table $planId = $customSettings->value_str; // get plan $plan = Plan::where('id', $planId)->first(); $newDiscountedPrice = $plan->price; $taxRate = $gateway->tax; $taxValue = taxToVal($plan->price, $taxRate); $newDiscountedPrice = $plan->price; session_start(); // Start the session if not already started if (isset($_SESSION['applied_coupon'])) { $appliedCouponData = $_SESSION['applied_coupon']; if ($appliedCouponData['plan_id'] == $planId) { $appliedCoupon = $appliedCouponData['coupon']; $newDiscountedPrice = $plan->price - ($plan->price * ($appliedCoupon->discount / 100)); $couponID = $appliedCoupon->discount; } unset($_SESSION['applied_coupon']); } session_write_close(); $newDiscountedPrice += $taxValue; // save checkout to orders $payment = new UserOrder; $payment->order_id = $checkoutresult->getPaymentId(); $payment->plan_id = $plan->id; $payment->type = 'prepaid'; $payment->user_id = $user->id; $payment->payment_type = self::$GATEWAY_CODE; $payment->price = $newDiscountedPrice; $payment->affiliate_earnings = ($newDiscountedPrice * $settings->affiliate_commission_percentage) / 100; $payment->status = 'Success'; $payment->country = $user->country ?? 'Unknown'; $payment->tax_rate = $taxRate; $payment->tax_value = $taxValue; $payment->save(); self::creditIncreaseSubscribePlan($user, $plan); // delete custom settings since we do not need it anymore $customSettings->delete(); \App\Models\Usage::getSingle()->updateSalesCount($newDiscountedPrice); CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Token Pack')); EmailPaymentConfirmation::create($user, $plan)->send(); } DB::commit(); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE . '.result', compact('plan', 'gateway', 'exception', 'currency', 'checkoutresult')); } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . '-> iyzicoPrepaidCallback(): ' . $ex->getMessage()); return back()->with(['message' => Str::before($ex->getMessage(), ':'), 'type' => 'error']); } } // other functions public static function subscribeCancel($internalUser = null): ?RedirectResponse { $user = $internalUser ?? Auth::user(); $iyzipayActions = self::retrieveGatewaySettings(); $activeSub = getCurrentActiveSubscription(); if ($activeSub) { $plan = Plan::where('id', $activeSub->plan_id)->first(); try { if ($activeSub->stripe_status === 'iyzico_approved') { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), 'Subscription plan'); if ($internalUser !== null) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } $cancelSubscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $cancelSubscription = $iyzipayActions->cancelSubscription($cancelSubscriptionRequest); if ($cancelSubscription) { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), $plan->name); if ($internalUser) { return back()->with(['message' => __('User subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Your subscription could not be cancelled.'), 'type' => 'error']); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } return back()->with(['message' => __('Your subscription not found.'), 'type' => 'error']); } public static function getDaysLeft($timestamp) { // function that returns days left from now to given timestamp, if timestamp is null or days left is less than 0, returns null if ($timestamp === null) { return null; } // Convert millisecond timestamp to seconds as iyzico sends timestamp in milliseconds $timestampInSeconds = $timestamp / 1000; $now = Carbon::now(); $ends = Carbon::createFromTimestamp($timestampInSeconds); $daysLeft = $now->diffInDays($ends, false); if ($daysLeft < 0) { return null; } return $daysLeft; } /** * @throws JsonException */ public static function getSubscriptionDaysLeft(): ?int { $iyzipayActions = self::retrieveGatewaySettings(); $userId = Auth::user()->id; // Get current active subscription $activeSub = getCurrentActiveSubscription(); if ($activeSub) { $plan = Plan::where('id', $activeSub->plan_id)->first(); if ($activeSub->stripe_status === 'iyzico_approved') { return Carbon::now()->diffInDays(Carbon::parse($activeSub->ends_at)); } $subscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscription = $iyzipayActions->getSubscriptionDetails($subscriptionRequest); if (Str::lower($subscription->getSubscriptionStatus()) === 'active') { if ($subscription->getTrialEndDate()) { $trialDaysLeft = self::getDaysLeft($subscription->getTrialEndDate()); if ($trialDaysLeft) { return $trialDaysLeft; } } else { $orders = $subscription->getOrders(); foreach ($orders as $iValue) { if ($iValue->orderStatus === 'WAITING') { // do nothing } else { $daysLeft = self::getDaysLeft($iValue->endPeriod); if ($daysLeft) { return $daysLeft; } return 0; } } } } else { return 0; } } return null; } /** * @throws JsonException */ public static function checkIfTrial(): bool { $iyzipayActions = self::retrieveGatewaySettings(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription(); if ($activeSub) { if ($activeSub->stripe_status === 'iyzico_approved') { return false; } $plan = Plan::where('id', $activeSub->plan_id)->first(); // get subscription $subscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscription = $iyzipayActions->getSubscriptionDetails($subscriptionRequest); if ((Str::lower($subscription->getSubscriptionStatus()) === 'active') && $subscription->getTrialEndDate()) { $trialDaysLeft = self::getDaysLeft($subscription->getTrialEndDate()); return $trialDaysLeft !== null; } return false; } return false; } /** * @throws JsonException */ public static function getSubscriptionRenewDate(): ?string { $iyzipayActions = self::retrieveGatewaySettings(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription(); if ($activeSub) { if ($activeSub->stripe_status === 'iyzico_approved') { return Carbon::createFromTimeStamp($activeSub->ends_at)->format('F jS, Y'); } $plan = Plan::where('id', $activeSub->plan_id)->first(); $subscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscription = $iyzipayActions->getSubscriptionDetails($subscriptionRequest); if (Str::lower($subscription->getSubscriptionStatus()) === 'active') { $orders = $subscription->getOrders(); foreach ($orders as $iValue) { if ($iValue->orderStatus === 'WAITING') { // } else { return Carbon::createFromTimestamp($iValue->endPeriod / 1000)->format('F jS, Y'); break; } } return Carbon::now()->format('F jS, Y'); } $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); return Carbon::now()->format('F jS, Y'); } return null; } /** * @throws JsonException */ public static function getSubscriptionStatus(): ?bool { $iyzipayActions = self::retrieveGatewaySettings(); $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription(); if ($activeSub) { $plan = Plan::where('id', $activeSub->plan_id)->first(); if ($activeSub->stripe_status === 'iyzico_approved') { // later we can renew from here or from command return true; } else { $subscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $subscription = $iyzipayActions->getSubscriptionDetails($subscriptionRequest); if (Str::lower($subscription->getSubscriptionStatus()) === 'active') { return true; } else { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); return false; } } } return null; } /** * @throws JsonException * @throws Exception */ public static function cancelSubscribedPlan($planId, $subsId) { $iyzipayActions = self::retrieveGatewaySettings(); $user = Auth::user(); $activeSub = getCurrentActiveSubscription(); if ($activeSub) { $plan = Plan::where('id', $planId)->first(); if ($activeSub->stripe_status === 'iyzico_approved') { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } else { // cancel subscription $cancelSubscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $activeSub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $cancelSubscription = $iyzipayActions->cancelSubscription($cancelSubscriptionRequest); if ($cancelSubscription) { $activeSub->stripe_status = 'cancelled'; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } } } return false; } public static function updateUserData(): void { $history = OldGatewayProducts::where([ 'gateway_code' => self::$GATEWAY_CODE, 'status' => 'check', ])->get(); if ($history) { $iyzipayActions = self::retrieveGatewaySettings(); foreach ($history as $record) { // check record current status from gateway $lookingFor = $record->old_price_id; // search subscriptions for record $subs = Subscriptions::where([ 'stripe_status' => 'active', 'stripe_price' => $lookingFor, ])->get(); if ($subs != null) { foreach ($subs as $sub) { // cancel subscription $cancelSubscriptionRequest = json_decode(json_encode([ 'subscriptionReferenceCode' => $sub->stripe_id, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $cancelSubscription = $iyzipayActions->cancelSubscription($cancelSubscriptionRequest); // cancel subscription from our database $sub->stripe_status = 'cancelled'; $sub->ends_at = Carbon::now(); $sub->save(); } } $record->status = 'checked'; $record->save(); } } } /** * @throws JsonException */ public static function iyzicoProductsList(): bool|string { $iyzipayActions = self::retrieveGatewaySettings(); $req = json_decode(json_encode([ 'itemPage' => 1, 'itemCount' => 100, ], JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR); $products = $iyzipayActions->listSubscriptionProducts($req); return json_encode($products, JSON_THROW_ON_ERROR); } public static function verifyIncomingJson(Request $request): bool { $gateway = Gateways::where('code', self::$GATEWAY_CODE)->first(); if ($gateway === null) { abort(404); } $currentSubscription = getCurrentActiveSubscription(); try { // below check mechanism is set from https://dev.iyzipay.com/tr/webhooks to check regular webhooks // but after consulting to customer service of iyzico, we learned that we get different json data from regular webhooks for recurring payments. // as of last mail - pasted below - we can not use this validation mechanism for recurring payments. // hence we can only check if incoming json is valid - contains all fields - or not. // ------------------------------------------------------- /* Webhook için farklı bir dokümanımız maalesef bulunmamaktadır. Mevcut standart webhook bildirimi için validasyon gerçekleştirebilirsiniz ancak subscription tekrarlı ödemelerin webhook bildiriminde dönen değerler farklı olduğundan dolayı bu kısımda ek olarak bir doküman bulunmamaktadır. https://docs.iyzico.com/ek-servisler/webhook https://dev.iyzipay.com/tr/webhooks */ // ------------------------------------------------------- $payload = json_decode($request->getContent(), false, 512, JSON_THROW_ON_ERROR); if (! $payload->orderReferenceCode || ! $payload->customerReferenceCode || ! $payload->subscriptionReferenceCode || ! $payload->iyziReferenceCode || ! $payload->iyziEventType || ! $payload->iyziEventTime) { return false; } if (Carbon::parse($currentSubscription->created_at)->diffInMinutes(Carbon::parse($payload->create_time)) < 5) { return false; } // / below code is not applicable for recurring payments, please see the comment above if ($request->hasHeader('X-IYZ-SIGNATURE')) { $incoming_signature = $request->header('X-IYZ-SIGNATURE'); } else { return false; } $payload = json_decode($request->getContent()); // get secret key from gateway according to mode $secretKey = $gateway->mode === 'sandbox' ? $gateway->sandbox_client_secret : $gateway->live_client_secret; // get iyziEventType from payload if ($payload->iyziEventType) { $iyziEventType = $payload->iyziEventType; } else { return false; } // get iyziReferenceCode from payload if ($payload->iyziReferenceCode) { $iyziReferenceCode = $payload->iyziReferenceCode; } else { return false; } // Concatenate the values $stringToBeHashed = $secretKey . $iyziEventType . $iyziReferenceCode; // Log::info("stringToBeHashed : " . $stringToBeHashed); // Hash the concatenated string using SHA-1 and then base64 encode it $hash = base64_encode(sha1($stringToBeHashed, true)); // Log::info("hash : " . $hash); // Compare the hash with the incoming signature return $hash === $incoming_signature; } catch (Exception $th) { Log::error('(Webhooks) IyzicoService::verifyIncomingJson(): ' . $th->getMessage()); } return false; } public function handleWebhook(Request $request) { $verified = self::verifyIncomingJson($request); if ($verified) { // Retrieve the JSON payload $payload = $request->getContent(); // Fire the event with the payload event(new IyzicoWebhookEvent($payload)); return response()->json(['success' => true]); } abort(404); } // helper functions private static function getIyzicoPriceId($planId) { $plan = Plan::where('id', $planId)->first(); if ($plan) { $product = GatewayProducts::where(['plan_id' => $planId, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product) { return $product->price_id; } return null; } return null; } private static function retrieveGatewaySettings($gateway = null) { $gateway = $gateway ?? Gateways::where('code', self::$GATEWAY_CODE)->where('is_active', 1)->first() ?? abort(404); $currency = Currency::where('id', $gateway->currency)->first()->code; $data = [ 'apiKey' => $gateway->mode === 'live' ? $gateway->live_client_id : $gateway->sandbox_client_id, 'apiSecretKey' => $gateway->mode === 'live' ? $gateway->live_client_secret : $gateway->sandbox_client_secret, 'baseUrl' => $gateway->mode === 'live' ? $gateway->base_url : $gateway->sandbox_url, 'currency' => $currency, ]; return new IyzipayActions($data['apiKey'], $data['apiSecretKey'], $data['baseUrl'], Locale::TR, $data['currency']); } public static function gatewayDefinitionArray(): array { return [ 'code' => 'iyzico', 'title' => 'iyzico', 'link' => 'https://www.iyzico.com/', 'active' => 0, 'available' => 1, 'img' => '/assets/img/payments/iyzico.svg', 'whiteLogo' => 0, 'mode' => 1, 'sandbox_client_id' => 1, 'sandbox_client_secret' => 1, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 1, 'live_app_id' => 0, 'currency' => 1, 'currency_locale' => 0, 'notify_url' => 0, 'base_url' => 1, 'sandbox_url' => 1, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } RevenueCatService.php000064400000054742152667044740010672 0ustar00id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($activeSub->stripe_status == 'trialing') { return \Carbon\Carbon::parse($activeSub->trial_ends_at)->diffInDays(); } else { return \Carbon\Carbon::parse($activeSub->ends_at)->diffInDays(); } } return null; } public static function getSubscriptionRenewDate() { $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($activeSub->stripe_status == 'trialing') { return \Carbon\Carbon::parse($activeSub->trial_ends_at)->format('F jS, Y'); } else { return \Carbon\Carbon::parse($activeSub->ends_at)->format('F jS, Y'); } } return null; } public static function getSubscriptionStatus($fromApi = false) { // / Developer Note: // / This functions returns true if user has an active subscription, otherwise returns false and null for errors // / If fromApi is true, then it returns json response instead of true/false/null // / Api responses are 4 types: 200, 404, 412, 500 // / 200: Active subscription found // / 404: No active subscription found // / 412: Error occured // / 500: Error occured // / Mobile app displays returned error messages for 412 and 500 responses, but not for 404. Hence, do not change the messages and codes for these responses $user = Auth::user(); $userId = $user->id; // Since this is RevenueCat (updates are made outside of our system), we need to get the subscription status from the RevenueCat API first // Get api key from gateway settings $gateway = Gateways::where('code', 'revenuecat')->first(); if ($gateway == null) { Log::error('Gateway not found. RevenueCatService@getSubscriptionStatus'); if ($fromApi) { return response()->json(['message' => 'Gateway not found.'], 412); } else { return null; } } $apiKey = $gateway->live_client_id; if ($apiKey == null) { Log::error('Gateway is not set properly. RevenueCatService@getSubscriptionStatus'); if ($fromApi) { return response()->json(['message' => 'Gateway is not set properly.'], 412); } else { return null; } } $settings = Setting::getCache(); // Get user's revenuecat id $user = Auth::user(); $userId = $user->id; $userRevenueCatId = $user->revenuecat_id; if ($userRevenueCatId == null) { Log::error('User is not set properly. User must login at least once from mobile app. RevenueCatService@getSubscriptionStatus'); if ($fromApi) { return response()->json(['message' => 'User is not set properly. User must login at least once from mobile app.'], 412); } else { return null; } } // Get subscription status from RevenueCat API $url = 'https://api.revenuecat.com/v1/subscribers/' . $userRevenueCatId; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ]); $response = curl_exec($ch); curl_close($ch); if ($response == null || $response == '' || $response == '{}') { Log::error('Error getting subscription status from RevenueCat API. RevenueCatService@getSubscriptionStatus'); if ($fromApi) { return response()->json(['message' => 'Error getting subscription status from RevenueCat API.'], 500); } else { return null; } } $response = json_decode($response, true); $subscriber = $response['subscriber']; // Log::info('RevenueCat API Response: ' . json_encode($response)); // ///////////////////////// // / TOKEN PACKS // ///////////////////////// $tokenPacks = $subscriber['non_subscriptions']; // check if all token packs are applied and saved to database /* "non_subscriptions": { "onetime": [ { "id": "cadba0c81b", "is_sandbox": true, "purchase_date": "2019-04-05T21:52:45Z", "store": "app_store" } ], }, */ foreach ($tokenPacks as $identifier => $purchasedPacksArray) { foreach ($purchasedPacksArray as $pack) { $orders = UserOrder::where([['user_id', '=', $userId], ['order_id', '=', $pack['id']]])->get(); if (count($orders) == 0) { // Create order $revenueCatProduct = RevenueCatProducts::where('google_id', $identifier)->first(); if ($revenueCatProduct == null) { $revenueCatProduct = RevenueCatProducts::where('apple_id', $identifier)->first(); if ($revenueCatProduct == null) { Log::error('RevenueCat product not found. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'RevenueCat product not found.'], 412); } else { return null; } } } $gatewayProduct = $revenueCatProduct->gateway_product; if ($gatewayProduct == null) { Log::error('Gateway product not found for RevenueCat. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'Gateway product not found.'], 412); } else { return null; } } $plan = $revenueCatProduct->plan; if ($plan == null) { Log::error('Plan not found for RevenueCat. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'Plan not found.'], 412); } else { return null; } } $order = new UserOrder; $order->order_id = $pack['id']; $order->plan_id = $plan->id; $order->type = 'prepaid'; $order->user_id = $userId; $order->payment_type = 'RevenueCat'; $order->price = $plan->price; // users must set coupons at stores - we are not allowed from external source $order->affiliate_earnings = ($plan->price * $settings->affiliate_commission_percentage) / 100; $order->status = 'Success'; $order->country = $user->country ?? 'Unknown'; $order->save(); self::creditIncreaseSubscribePlan($user, $plan); CreateActivity::for($user, __('Purchased'), $plan->name . ' ' . __('Token Pack')); EmailPaymentConfirmation::create($user, $plan)->send(); } else { // / Order already exists, do nothing } } } // ///////////////////////// // / SUBSCRIPTIONS // ///////////////////////// $subscriptions = $subscriber['subscriptions']; /* "rc_promo_pro_cat_monthly": { "auto_resume_date": null, "billing_issues_detected_at": null, "expires_date": "2019-08-26T01:02:16Z", "grace_period_expires_date": null, "is_sandbox": false, "original_purchase_date": "2019-07-26T01:02:16Z", "ownership_type": "FAMILY_SHARED", "period_type": "normal", "purchase_date": "2019-07-26T01:02:16Z", "refunded_at": null, "store": "promotional", "unsubscribe_detected_at": null } */ // / We are going to use "purchase_date" instead of "original_purchase_date" because "original_purchase_date" is not updated when user renews the subscription foreach ($subscriptions as $identifier => $subs) { $orderId = (string) $subs['purchase_date']; $orderId = str_replace('-', '', $orderId); $orderId = str_replace(':', '', $orderId); $orderId = str_replace(' ', '', $orderId); $orderId = str_replace('T', '', $orderId); $orderId = str_replace('Z', '', $orderId); $revenueCatProduct = RevenueCatProducts::where('google_id', $identifier)->first(); if ($revenueCatProduct == null) { $revenueCatProduct = RevenueCatProducts::where('apple_id', $identifier)->first(); if ($revenueCatProduct == null) { Log::error('RevenueCat product not found. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'RevenueCat product not found.'], 412); } else { return null; } } } $gatewayProduct = $revenueCatProduct->gateway_product; if ($gatewayProduct == null) { Log::error('Gateway product not found for RevenueCat. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'Gateway product not found.'], 412); } else { return null; } } $plan = $revenueCatProduct->plan; if ($plan == null) { Log::error('Plan not found for RevenueCat. Identifier: ' . $identifier); if ($fromApi) { return response()->json(['message' => 'Plan not found.'], 412); } else { return null; } } $planId = $plan->id; $orders = UserOrder::where([['user_id', '=', $userId], ['order_id', '=', $orderId]])->get(); if (count($orders) == 0) { // Create order $order = new UserOrder; $order->order_id = $orderId; $order->plan_id = $planId; $order->type = 'subscription'; $order->user_id = $userId; $order->payment_type = 'RevenueCat'; $order->price = $plan->price; // users must set coupons at stores - we are not allowed from external source $order->affiliate_earnings = ($plan->price * $settings->affiliate_commission_percentage) / 100; $order->status = 'Success'; $order->country = $user->country ?? 'Unknown'; $order->save(); $status = 'cancelled'; $isRefreshed = false; // / Check if there is currently active subscription (in case of refresh/renew, orderId will be updated but plan remains the same) // / This is to prevent multiple active subscriptions for the same plan $oldSubscription = Subscriptions::where([['user_id', '=', $userId], ['plan_id', '=', $planId], ['stripe_status', '=', 'active']])->first(); if ($oldSubscription != null) { // There is an active subscription, cancel it $oldSubscription->stripe_status = 'cancelled'; $oldSubscription->save(); $isRefreshed = true; } if ($subs['unsubscribe_detected_at'] == null) { // Subscription is active $isTrial = $subs['period_type'] == 'trial'; $status = $subs['billing_issues_detected_at'] == null ? ($isTrial == true ? 'trialing' : 'active') : 'cancelled'; if ($subs['billing_issues_detected_at'] != null) { Log::error('Billing issue detected at RevenueCat. User ID: ' . $userId . ' Subscription: ' . $identifier); } self::creditIncreaseSubscribePlan($user, $plan); // / Plan is active, and we haven't added to orders before; so this is a new subscription. Hence add the plan to user's remaining words and images CreateActivity::for($user, ! $isRefreshed ? __('Subscribed') : __('Renewed'), $plan->name . ' ' . __('Plan')); EmailPaymentConfirmation::create($user, $plan)->send(); } else { // Subscription is cancelled if ($subs['billing_issues_detected_at'] != null) { Log::error('Billing issue detected at RevenueCat. User ID: ' . $userId . ' Subscription: ' . $identifier); } $planFrequency = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'month' : 'year'; if (Carbon::parse($subs['unsubscribe_detected_at'])->isBefore(Carbon::parse($subs['purchase_date'])->add(1, $planFrequency))) { // / Subscription is cancelled before the end date (user cancelled it) // / Since user cancelled it, we need to remove the plan from user's remaining words and images // / Check if subscription with orderId is still active (in case of cancelled before) $subscription = Subscriptions::where([['user_id', '=', $userId], ['stripe_id', '=', $orderId]])->first(); if ($subscription != null) { if ($subscription->stripe_status == 'active' || $subscription->stripe_status == 'trialing') { self::creditDecreaseCancelPlan($user, $plan); // / This is to cancel the subscription only once (orderId is unique and it is active/trial) CreateActivity::for($user, __('Cancelled'), 'Subscription plan'); } } } } // / check if subscription is already added $subscription = Subscriptions::where([['user_id', '=', $userId], ['stripe_id', '=', $orderId]])->first(); if ($subscription != null) { // Subscription is already added, update it $subscription->stripe_status = $status; $subscription->trial_ends_at = Carbon::parse($subs['expires_date']); $subscription->ends_at = Carbon::parse($subs['expires_date']); $subscription->save(); } else { // Subscription is not added, add it $subscription = new Subscriptions; $subscription->user_id = $userId; $subscription->name = $planId; $subscription->stripe_id = $orderId; $subscription->stripe_status = $status; $subscription->stripe_price = $gatewayProduct->price_id; $subscription->quantity = 1; $subscription->trial_ends_at = Carbon::parse($subs['expires_date']); $subscription->ends_at = Carbon::parse($subs['expires_date']); $subscription->plan_id = $planId; $subscription->paid_with = 'revenuecat'; $subscription->save(); // $subscriptionItem = new SubscriptionItems(); // $subscriptionItem->subscription_id = $subscription->id; // $subscriptionItem->stripe_id = $orderId; // $subscriptionItem->stripe_product = $gatewayProduct->product_id; // $subscriptionItem->stripe_price = $gatewayProduct->price_id; // $subscriptionItem->quantity = 1; // $subscriptionItem->save(); } $user->save(); } else { // / Order already exists, but maybe subscription is cancelled $isTrial = $subs['period_type'] == 'trial'; $status = ($subs['billing_issues_detected_at'] == null && $subs['unsubscribe_detected_at'] == null) ? ($isTrial == true ? 'trialing' : 'active') : 'cancelled'; if ($subs['unsubscribe_detected_at'] != null) { // Subscription is cancelled if ($subs['billing_issues_detected_at'] != null) { Log::error('Billing issue detected at RevenueCat. User ID: ' . $userId . ' Subscription: ' . $identifier); } $planFrequency = $plan->frequency == FrequencyEnum::MONTHLY->value ? 'month' : 'year'; if (Carbon::parse($subs['unsubscribe_detected_at'])->isBefore(Carbon::parse($subs['purchase_date'])->add(1, $planFrequency))) { // / Subscription is cancelled before the end date (user cancelled it) // / Since user cancelled it, we need to remove the plan from user's remaining words and images // / Check if subscription with orderId is still active (in case of cancelled before) $subscription = Subscriptions::where([['user_id', '=', $userId], ['stripe_id', '=', $orderId]])->first(); if ($subscription != null) { if ($subscription->stripe_status == 'active' || $subscription->stripe_status == 'trialing') { // / This is to cancel the subscription only once (orderId is unique and it is active/trial) self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, __('Cancelled'), 'Subscription plan'); } } } } // / check if subscription is already added $subscription = Subscriptions::where([['user_id', '=', $userId], ['stripe_id', '=', $orderId]])->first(); if ($subscription == null) { // Subscription is added while creating order Log::error('Subscription is added while creating order. But not found on checks. User ID: ' . $userId . ' Subscription: ' . $identifier); } else { // Subscription is already added, update it $subscription->stripe_status = $status; $subscription->trial_ends_at = Carbon::parse($subs['expires_date']); $subscription->ends_at = Carbon::parse($subs['expires_date']); $subscription->save(); } $user->save(); } } // ///////////////////////// // / Prepare Result // ///////////////////////// // / We have checked both subscriptions and token packs, now we need to check if there is any active subscription and return the result // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { if ($fromApi) { return response()->json(['message' => __('Active subscription found.'), 'status' => 'active'], 200); } return true; } if ($fromApi) { return response()->json(['message' => 'No active subscription found.'], 404); } return false; } public static function checkIfTrial() { $userId = Auth::user()->id; $activeSub = getCurrentActiveSubscription($userId); if ($activeSub != null) { return $activeSub->stripe_status == 'trialing'; } return false; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'revenuecat', 'title' => 'RevenueCat', 'link' => 'https://www.revenuecat.com/', 'active' => 1, 'available' => 1, 'img' => '/assets/img/payments/revenuecat.png', 'whiteLogo' => 0, 'mode' => 0, 'sandbox_client_id' => 0, 'sandbox_client_secret' => 0, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 0, 'live_app_id' => 0, 'currency' => 0, 'currency_locale' => 0, 'notify_url' => 0, 'base_url' => 0, 'sandbox_url' => 0, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } } RazorpayService.php000064400000053704152667044740010435 0ustar00with(['message' => __('Please enable Razorpay'), 'type' => 'error']); } Plan::query() ->where('currency', 'INR') ->where('type', '!=', 'prepaid') ->where('active', 1) ->each(fn ($plan) => self::saveProduct($plan)); return null; } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan): bool|\Illuminate\Http\RedirectResponse { try { $gateway = self::geteway(); if (! $gateway) { return back()->with(['message' => __('Please enable Razorpay'), 'type' => 'error']); } $api = self::client(); DB::beginTransaction(); $data = $api->plan->create( self::planPayload($plan) ); GatewayProducts::updateOrCreate( ['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE], ['plan_name' => $plan->name, 'product_id' => $data['id'], 'price_id' => $data['item']['id'], 'payload' => $data->toArray()] ); DB::commit(); return true; } catch (Exception $ex) { DB::rollBack(); Log::error(self::$GATEWAY_CODE . "-> saveProduct():\n" . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function client(): Api { $gateway = self::geteway(); $apiKey = $gateway->getAttribute('mode') === 'sandbox' ? $gateway->getAttribute('sandbox_client_id') : $gateway->getAttribute('live_client_id'); $secret = $gateway->getAttribute('mode') === 'sandbox' ? $gateway->getAttribute('sandbox_client_secret') : $gateway->getAttribute('live_client_secret'); return new Api($apiKey, $secret); } public static function geteway(): Model|Builder|null { if (self::$gateway) { return self::$gateway; } self::$gateway = Gateways::where('code', self::$GATEWAY_CODE)->first(); return self::$gateway; } public static function objectToArray($request) { if (is_object($request)) { if (property_exists($request, 'id')) { $request = json_decode(json_encode($request), true); } } return $request; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'razorpay', 'title' => 'Razorpay', 'link' => 'https://razorpay.com/', 'active' => 0, 'available' => 1, 'img' => '/assets/img/payments/razorpay.svg', 'whiteLogo' => 0, 'mode' => 1, 'sandbox_client_id' => 1, 'sandbox_client_secret' => 1, 'sandbox_app_id' => 0, 'live_client_id' => 1, 'live_client_secret' => 1, 'live_app_id' => 0, 'currency' => 0, 'currency_locale' => 0, 'notify_url' => 0, 'base_url' => 0, 'sandbox_url' => 0, 'locale' => 0, 'validate_ssl' => 0, 'webhook_secret' => 0, 'logger' => 0, 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } public static function subscribe($plan): View { $order_id = 'ORDER-' . strtoupper(Str::random(13)); $newDiscountedPrice = $plan->price; $coupon = checkCouponInRequest(); if ($coupon) { $newDiscountedPrice = $plan->price - ($plan->price * ($coupon->discount / 100)); if ($newDiscountedPrice != floor($newDiscountedPrice)) { $newDiscountedPrice = number_format($newDiscountedPrice, 2); } } return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'newDiscountedPrice', 'order_id', 'coupon')); } public static function subscribeCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $plan = Plan::query() ->where('id', $planID) ->first(); $gateway = self::geteway(); if ($gateway == null) { return back()->with(['message' => __('Please enable coingate'), 'type' => 'error']); } $gatewayProduct = GatewayProducts::query() ->where('plan_id', $plan->getAttribute('id')) ->where('gateway_code', self::$GATEWAY_CODE) ->first(); if (! $gatewayProduct) { return back()->with(['message' => __('Gateway product not found'), 'type' => 'error']); } $planId = $gatewayProduct->getAttribute('product_id'); try { $api = self::client(); $data = $api->subscription->create([ 'plan_id' => $planId, 'total_count' => 6, 'quantity' => 1, 'customer_notify' => 0, ]); Subscriptions::query() ->create([ 'user_id' => auth()->id(), 'name' => $plan->id, 'stripe_id' => $data['id'], 'stripe_status' => 'WAITING', 'stripe_price' => null, 'quantity' => 1, 'trial_ends_at' => null, 'tax_rate' => 0, 'tax_value' => 0, 'coupon' => null, 'total_amount' => $plan->price, 'plan_id' => $plan->id, 'paid_with' => self::$GATEWAY_CODE, ]); $order = UserOrder::query() ->create([ 'order_id' => $data['id'], 'plan_id' => $plan->id, 'user_id' => auth()->id(), 'payment_type' => self::$GATEWAY_CODE, 'price' => $plan->price, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'type' => 'subscription', 'tax_value' => 0, 'payload' => $data->toArray(), ]); \App\Models\Usage::getSingle()->updateSalesCount($plan->price); $short_link = $data['short_url']; return redirect($short_link); } catch (Exception $exception) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $exception->getMessage()); return back()->with(['message' => Str::before($exception->getMessage(), ':'), 'type' => 'error']); } } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $plan = Plan::query()->where('id', $planID)->first(); $user = Auth::user(); $gateway = self::geteway(); if ($gateway === null) { return back()->with(['message' => __('Please enable coingate'), 'type' => 'error']); } $api = self::client(); try { $order = UserOrder::query() ->create([ 'order_id' => $orderID, 'plan_id' => $plan->id, 'user_id' => $user->id, 'payment_type' => self::$GATEWAY_CODE, 'price' => $plan->price, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'tax_value' => 0, 'type' => 'token-pack', 'payload' => [], ]); $paymentLink = $api->paymentLink->create([ 'amount' => $plan->price * 100, 'currency' => 'INR', 'accept_partial' => true, 'reference_id' => $orderID, 'description' => 'For XYZ purpose', 'customer' => [ 'name' => auth()->user()->name, 'email' => auth()->user()->email, ], 'notify' => ['sms' => false, 'email' => true], 'reminder_enable' => true, 'callback_url' => Helper::setting('site_url') . '/webhooks/razorpay', 'callback_method' => 'get', ]); // 9876543XXX $order->update([ 'order_id' => $paymentLink['id'], // 'id' => 'pl_1J2f3g4h5I6j7k8l9', 'payload' => $paymentLink->toArray(), ]); \App\Models\Usage::getSingle()->updateSalesCount($plan->price); return redirect()->to($paymentLink['short_url']); } catch (Exception $exception) { Log::error(self::$GATEWAY_CODE . '-> prepaidCheckout(): ' . $exception->getMessage()); return back()->with(['message' => Str::before($exception->getMessage(), ':'), 'type' => 'error']); } } public static function prepaid($plan) { $order_id = 'ORDER-' . strtoupper(Str::random(13)); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'order_id')); } public static function subscribeCancel(?User $internalUser = null) { $user = Auth::user() ?: $internalUser; $userId = $user->getAttribute('id'); // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if (! $activeSub) { return; } /** * @var Plan $plan */ $plan = Plan::query() ->where('id', $activeSub->getAttribute('plan_id')) ->first(); $api = self::client(); $data = $api->subscription->fetch($activeSub->getAttribute('stripe_id'))->cancel([]); if ($data) { $status = data_get($data, 'status'); if ($status === 'cancelled') { $activeSub->stripe_status = $status; $activeSub->ends_at = Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function cancelSubscribedPlan($subscription, $planId) { $api = self::client(); $user = Auth::user(); $check = $subscription instanceof Subscriptions; if (! $check) { $subscription = Subscriptions::where('id', $subscription)->first(); } if ($subscription === null) { return back()->with(['message' => __('Subscription not found'), 'type' => 'error']); } try { $data = $api->subscription->fetch($subscription->getAttribute('stripe_id'))->cancel([]); $subscription->update([ 'stripe_status' => 'canceled', ]); } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> cancelSubscribedPlan(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function checkIfTrial(): bool { return true; } public static function getSubscriptionRenewDate() { $user = Auth::user() ?: User::query()->first(); $subscription = getCurrentActiveSubscription($user->getAttribute('id')); $next_delivery_date = null; if ($subscription) { $id = $subscription->getAttribute('stripe_id'); $request = self::client()->subscription->fetch($id); $next_delivery_date = data_get($request, 'charge_at'); } if ($next_delivery_date) { return date('F jS, Y', $next_delivery_date); } return false; } public static function getSubscriptionStatus($incomingUserId = null) { $user = null; if ($incomingUserId) { $user = User::query()->where('id', $incomingUserId)->first(); } if (Auth::check() && $incomingUserId == null) { $user = Auth::user(); } if (! $user) { return false; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_id'); try { $request = self::client()->subscription->fetch($id); $status = data_get($request, 'status'); if ((string) $status === 'active') { return true; } else { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => Carbon::now(), ]); } return false; } } catch (Exception $th) { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => Carbon::now(), ]); } return false; } } return false; } public static function getSubscriptionDaysLeft() { $user = null; if (Auth::check()) { $user = Auth::user(); } if (! $user) { return; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_id'); try { $request = self::client()->subscription->fetch($id); $next_delivery_date = data_get($request, 'charge_at'); if ($next_delivery_date) { $date = date('Y-m-d H:i:s', $next_delivery_date); $next_delivery_date = Carbon::parse($date); return $next_delivery_date->diffInDays(Carbon::now()); } } catch (Exception $th) { return false; } } } public static function handleWebhook(Request $request) { $data = $request->all(); $event = data_get($data, 'event'); $entity = data_get($data, 'payload.subscription.entity'); if ($entity && ($event === 'subscription.activated' || $event === 'subscription.updated')) { $subscription = Subscriptions::query() ->where('stripe_id', data_get($entity, 'id')) ->first(); if ($subscription) { $subscription->update(['stripe_status' => data_get($entity, 'status')]); if (data_get($entity, 'status') === 'active') { /** * @var User $user */ $user = User::query()->where('id', $subscription->getAttribute('user_id'))->first(); /** * @var Plan $plan */ $plan = Plan::query()->where('id', $subscription->getAttribute('plan_id'))->first(); self::creditIncreaseSubscribePlan($user, $plan); } } } $razorpay_payment_link_id = data_get($data, 'razorpay_payment_link_id'); $razorpay_payment_link_status = data_get($data, 'razorpay_payment_link_status'); $razorpay_payment_link_reference_id = data_get($data, 'razorpay_payment_link_reference_id'); if ($razorpay_payment_link_id && $razorpay_payment_link_status && $razorpay_payment_link_reference_id) { $order = UserOrder::query() ->where('order_id', $razorpay_payment_link_id) ->where('type', 'token-pack') ->where('status', 'WAITING') ->first(); $api = self::client(); $data = $api->paymentLink->fetch($order->order_id); $status = $razorpay_payment_link_status; if ($status === 'paid') { /** * @var User $user */ $user = User::query()->where('id', $order->getAttribute('user_id'))->first(); /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->getAttribute('plan_id'))->first(); $order->update(['status' => 'PAID']); self::creditIncreaseSubscribePlan($user, $plan); return redirect('dashboard/user')->with(['message' => __('Payment successful'), 'type' => 'success']); } return redirect('dashboard/user')->with(['message' => __('Payment unsuccessful'), 'type' => 'error']); } return response()->json(['success' => true]); } public static function checkPayments(): void { $date = now()->subHours(2); $orders = UserOrder::query() ->where('payment_type', self::$GATEWAY_CODE) ->where('status', 'WAITING') ->where('created_at', '>', $date) ->get(); foreach ($orders as $order) { $payments = $order->payload; /** * @var User $user */ $user = User::query()->where('id', $order->getAttribute('user_id'))->first(); /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->getAttribute('plan_id'))->first(); $last = Arr::last($payments); try { if ($order->type === 'token-pack') { $api = self::client(); $data = $api->paymentLink->fetch($order->order_id); $status = data_get($data, 'status'); if ($status === 'paid') { $order->update(['status' => 'PAID']); self::creditIncreaseSubscribePlan($user, $plan); } } else { $request = self::client()->subscription->fetch($order->order_id); $subscription = Subscriptions::query() ->where('stripe_id', $order->order_id) ->first(); $status = data_get($request, 'status'); if ($subscription) { $subscription->stripe_status = $status; $subscription->save(); if ($status === 'active') { self::creditIncreaseSubscribePlan($user, $plan); } } } } catch (Exception $th) { } } } public static function planPayload(Plan $plan): array { return [ 'period' => $plan->getAttribute('frequency'), 'interval' => 1, 'item' => [ 'name' => $plan->getAttribute('name'), 'description' => $plan->getAttribute('name'), 'amount' => (int) $plan->getAttribute('price') * 100, 'currency' => 'INR', ], 'notes' => [], ]; } } CoingateService.php000064400000063337152667044740010362 0ustar00with(['message' => __('Please enable coingate'), 'type' => 'error']); } $plans = Plan::query()->where('active', 1)->get(); foreach ($plans as $plan) { self::saveProduct($plan); } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveAllProducts(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function getPlansPriceIdsForMigration(): null { return null; } public static function getUsersCustomerIdsForMigration(Subscriptions $subscription): null { return null; } public static function saveProduct($plan) { $client = self::client(); try { $request = $client->request('post', 'api/v2/billing/details', [ 'payment_method' => FrequencyEnum::MONTHLY->value, 'price_currency' => $plan->currency, 'receive_currency' => $plan->currency, 'title' => $plan->name, 'description' => $plan->name, 'details_id' => $plan->id, 'callback_url' => Helper::setting('site_url') . '/webhooks/coingate', 'send_paid_notification' => true, 'send_payment_email' => false, 'underpaid_cover_pct' => '"0.0"', 'items' => [ [ 'description' => $plan->name . ' item', 'price' => $plan->getAttribute('price'), 'currency' => $plan->getAttribute('currency'), 'quantity' => 1, 'item_id' => $plan->id, ], ], ]); $id = null; $request = self::objectToArray($request); if (is_array($request)) { if (array_key_exists('id', $request)) { $id = $request['id']; } } if ($id) { $product = GatewayProducts::query() ->firstOrCreate([ 'plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE, 'gateway_title' => self::$GATEWAY_NAME, ]); $oldProductId = $product->product_id; $wasRecentlyCreated = $product->wasRecentlyCreated; $product->update([ 'plan_name' => $plan->name, 'product_id' => $id, 'payload' => $request, ]); if (! $wasRecentlyCreated && $oldProductId) { OldGatewayProducts::query()->firstOrCreate([ 'plan_id' => $plan->id, 'plan_name' => $plan->name, 'gateway_code' => self::$GATEWAY_CODE, 'product_id' => $product->product_id, 'old_product_id' => $oldProductId, 'status' => 'check', ]); } } } catch (Exception $ex) { Log::error(self::$GATEWAY_CODE . '-> saveProduct(): ' . $ex->getMessage()); return back()->with(['message' => $ex->getMessage(), 'type' => 'error']); } } public static function subscribe($plan): View { $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); } $order_id = 'ORDER-' . strtoupper(Str::random(13)); $newDiscountedPrice = null; return view('panel.user.finance.subscription.' . self::$GATEWAY_CODE, compact('plan', 'order_id', 'newDiscountedPrice')); } public static function subscribeCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $couponID = $request->input('couponID', null); $plan = Plan::query()->where('id', $planID)->first(); $total = $plan->price; $user = self::createSubscriber(); $gatewayProduct = GatewayProducts::query() ->where('plan_id', $plan->getAttribute('id')) ->where('gateway_code', self::$GATEWAY_CODE) ->first(); if (! $gatewayProduct) { return back()->with(['message' => __('Gateway product not found'), 'type' => 'error']); } $client = self::client(); $request = $client->request('POST', 'api/v2/billing/subscriptions', [ 'subscription_id' => 'TEST-' . $user->id, 'subscriber' => $user->getAttribute('coingate_subscriber_id'), 'details' => $gatewayProduct->getAttribute('product_id'), 'start_date' => date('Y-m-d H:i:s'), ]); $request = self::objectToArray($request); if (array_key_exists('id', $request)) { $id = $request['id']; // payment activate $client->request( 'PATCH', 'api/v2/billing/subscriptions/' . $id . '/activate', [ 'id' => $id, ]); $payment = $client->request( 'GET', '/api/v2/billing/subscriptions/' . $id . '/payments', [ 'id' => $id, ]); $payment = self::objectToArray($payment); if (array_key_exists('id', $payment)) { $payments = data_get($payment, 'payments'); if (is_array($payments) && count($payments) > 0) { $last = Arr::last($payments); $payment_url = data_get($last, 'payment_url'); if ($payment_url) { try { Subscriptions::query() ->create([ 'user_id' => $user->id, 'name' => $plan->id, 'stripe_id' => $id, 'stripe_status' => 'WAITING', 'stripe_price' => $payment['id'], 'quantity' => 1, 'trial_ends_at' => null, 'tax_rate' => 0, 'tax_value' => 0, 'coupon' => null, 'total_amount' => $total, 'plan_id' => $plan->id, 'paid_with' => self::$GATEWAY_CODE, ]); $order = UserOrder::query() ->create([ 'order_id' => $payment['id'], 'plan_id' => $plan->id, 'user_id' => $user->id, 'payment_type' => self::$GATEWAY_CODE, 'price' => $total, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'tax_value' => 0, 'payload' => $payment, ]); // sent mail if required here later CreateActivity::for($order->user, __('Purchased'), $order->plan->name . ' ' . __('Plan') . ' ' . __('For free')); EmailPaymentConfirmation::create($user, $plan)->send(); \App\Models\Usage::getSingle()->updateSalesCount($total); return redirect($payment_url); } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . '-> subscribe(): ' . $th->getMessage()); return back()->with(['message' => Str::before($th->getMessage(), ':'), 'type' => 'error']); } } } } } return back()->with(['message' => __('Subscription failed'), 'type' => 'error']); } public static function handleWebhook(Request $request) { $orderId = $request->input('order_id', null); $token = $request->input('token', null); if ($orderId) { $order = UserOrder::query() ->where('type', 'token-pack') ->where('order_id', $orderId) ->where('payment_type', self::$GATEWAY_CODE) ->first(); if ($order) { $status = $request->input('status', null); if ($status == 'paid') { /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->plan_id)->first(); /** * @var User $user */ $user = User::query()->where('id', $order->user_id)->first(); self::creditIncreaseSubscribePlan($user, $plan); $order->update(['status' => 'PAID']); $subscription = Subscriptions::query() ->where('paid_with', self::$GATEWAY_CODE) ->where('stripe_id', $order->getAttribute('order_id')) ->first(); if ($subscription) { $subscription->stripe_status = 'active'; $subscription->save(); } } } } return true; } public static function prepaidCheckout(Request $request, $referral = null) { $planID = $request->input('planID', null); $orderID = $request->input('orderID', null); $plan = Plan::query()->where('id', $planID)->first(); $user = Auth::user(); $order = UserOrder::query() ->create([ 'order_id' => $orderID, 'plan_id' => $plan->id, 'user_id' => $user->id, 'payment_type' => self::$GATEWAY_CODE, 'price' => $plan->price, 'affiliate_earnings' => 0, 'status' => 'WAITING', 'country' => $user->country ?? 'Unknown', 'tax_rate' => 0, 'tax_value' => 0, 'type' => 'token-pack', 'payload' => [], ]); $client = self::client(); $request = $client->request('POST', 'api/v2/orders', [ 'order_id' => $orderID, 'price_amount' => $plan->price, 'price_currency' => $plan->currency, 'receive_currency' => $plan->currency, 'title' => $plan->name, 'description' => $plan->name, 'callback_url' => Helper::setting('site_url') . '/webhooks/coingate', 'cancel_url' => Helper::setting('site_url') . '/dashboard', 'success_url' => Helper::setting('site_url') . '/dashboard/user/payment/succesful', 'token' => base64_encode($orderID), 'purchaser_email' => $user->email, ]); $request = self::objectToArray($request); $order->update(['payload' => $request]); if (array_key_exists('id', $request)) { $id = $request['id']; $order->update(['order_id' => $id]); \App\Models\Usage::getSingle()->updateSalesCount($plan->price); return redirect(data_get($request, 'payment_url')); } return back()->with(['message' => __('Subscription failed'), 'type' => 'error']); } public static function prepaid($plan) { $product = GatewayProducts::where(['plan_id' => $plan->id, 'gateway_code' => self::$GATEWAY_CODE])->first(); if ($product == null) { self::saveProduct($plan); } $order_id = 'ORDER-' . strtoupper(Str::random(13)); return view('panel.user.finance.prepaid.' . self::$GATEWAY_CODE, compact('plan', 'order_id')); } public static function subscribeCancel(?User $internalUser = null) { $user = Auth::user() ?: $internalUser; $userId = $user->getAttribute('id'); // Get current active subscription $activeSub = getCurrentActiveSubscription($userId); if (! $activeSub) { return; } /** * @var Plan $plan */ $plan = Plan::query() ->where('id', $activeSub->getAttribute('plan_id')) ->first(); $id = $activeSub->getAttribute('stripe_price'); $client = self::client()->request('PATCH', 'api/v2/billing/subscriptions/' . $id . '/cancel', []); $request = self::objectToArray($client); $status = data_get($request, 'status'); if ($status == 'canceled') { $activeSub->stripe_status = $status; $activeSub->ends_at = \Carbon\Carbon::now(); $activeSub->save(); self::creditDecreaseCancelPlan($user, $plan); $user->save(); CreateActivity::for($user, 'Cancelled', 'Subscription plan'); return back()->with(['message' => __('Your subscription is cancelled succesfully.'), 'type' => 'success']); } return back()->with(['message' => __('Could not find active subscription. Nothing changed!'), 'type' => 'error']); } public static function cancelSubscribedPlan($subscription, $planId) { $user = Auth::user(); $check = $subscription instanceof Subscriptions; if (! $check) { $subscription = Subscriptions::where('id', $subscription)->first(); } try { $user = Auth::user(); $id = $subscription->getAttribute('stripe_price'); self::client()->request('PATCH', 'api/v2/billing/subscriptions/' . $id . '/cancel', []); $user->save(); return true; } catch (Exception $th) { Log::error(self::$GATEWAY_CODE . "-> cancelSubscribedPlan():\n" . $th->getMessage()); $plan = Plan::where('id', $planId)->first(); $subscription->stripe_status = 'cancelled'; $subscription->save(); self::creditDecreaseCancelPlan($user, $plan); return true; } } public static function checkIfTrial(): bool { return false; } public static function getSubscriptionRenewDate() { $user = Auth::user() ?: User::query()->first(); $subscription = getCurrentActiveSubscription($user->getAttribute('id')); $next_delivery_date = null; if ($subscription) { $id = $subscription->getAttribute('stripe_price'); $path = 'api/v2/billing/subscriptions/' . $id . ''; $client = self::client() ->request('get', $path, [ 'id' => $id, ]); $request = self::objectToArray($client); $next_delivery_date = data_get($request, 'next_delivery_date'); } if ($next_delivery_date) { return Carbon::create($next_delivery_date)->format('F jS, Y'); } return false; } public static function getSubscriptionStatus($incomingUserId = null): bool { $user = null; if ($incomingUserId) { $user = User::query()->where('id', $incomingUserId)->first(); } if (Auth::check() && $incomingUserId == null) { $user = Auth::user(); } if (! $user) { return false; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_price'); $path = 'api/v2/billing/subscriptions/' . $id; try { $client = self::client() ->request('get', $path, [ 'id' => $id, ]); $request = self::objectToArray($client); $status = data_get($request, 'status'); if ($status == 'active') { return true; } else { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => \Carbon\Carbon::now(), ]); } return false; } } catch (Exception $th) { if ($subscription->getAttribute('created_at') < Carbon::now()->subHours(2)) { $subscription->update([ 'stripe_status' => 'cancelled', 'ends_at' => \Carbon\Carbon::now(), ]); } return false; } } return false; } public static function getSubscriptionDaysLeft() { $user = null; if (Auth::check()) { $user = Auth::user(); } if (! $user) { return; } $subscription = getCurrentActiveSubscription($user->getAttribute('id')); if ($subscription) { $id = $subscription->getAttribute('stripe_price'); try { $path = 'api/v2/billing/subscriptions/' . $id; $client = self::client() ->request('get', $path, [ 'id' => $id, ]); $request = self::objectToArray($client); $next_delivery_date = data_get($request, 'next_delivery_date'); if ($next_delivery_date) { $next_delivery_date = Carbon::create($next_delivery_date); return $next_delivery_date->diffInDays(Carbon::now()); } } catch (Exception $th) { return false; } } } public static function client() { $gateway = self::geteway(); if (! $gateway) { return; } $mode = $gateway->getAttribute('mode') == 'sandbox'; $secret = $mode == 'sandbox' ? $gateway->getAttribute('sandbox_client_secret') : $gateway->getAttribute('live_client_secret'); $client = new \CoinGate\Client($secret, $mode); return $client; } public static function geteway(): Model|Builder|null { if (self::$gateway) { return self::$gateway; } self::$gateway = Gateways::where('code', 'coingate')->first(); return self::$gateway; } public static function createSubscriber() { $client = self::client(); $user = Auth::user() ?: User::query()->first(); if (is_null($user->coingate_subscriber_id)) { $request = $client->request('post', '/api/v2/billing/subscribers', [ 'email' => $user->email, 'subscriber_id' => Auth::id(), 'first_name' => $user->name, 'last_name' => $user->surname, ]); $request = self::objectToArray($request); if (array_key_exists('id', $request)) { $id = $request['id']; if ($id) { $user->update([ 'coingate_subscriber_id' => $id, ]); } } } return $user; } public static function checkPayments() { $date = now()->subHours(2); $orders = UserOrder::query() ->where('payment_type', self::$GATEWAY_CODE) ->where('status', 'WAITING') ->where('created_at', '>', $date) ->get(); foreach ($orders as $order) { $payments = $order->payload['payments']; $last = Arr::last($payments); $path = 'api/v2/billing/payments/' . $last['id']; try { $client = self::client() ->request('get', $path, [ 'id' => $last['id'], ]); $request = self::objectToArray($client); $status = data_get($request, 'status'); if ($status == 'paid') { /** * @var User $user */ $user = User::query()->where('id', $order->getAttribute('user_id'))->first(); /** * @var Plan $plan */ $plan = Plan::query()->where('id', $order->getAttribute('plan_id'))->first(); $order->update([ 'status' => 'PAID', ]); self::creditIncreaseSubscribePlan($user, $plan); $subscription = Subscriptions::query() ->where('stripe_id', $order->getAttribute('order_id')) ->first(); if ($subscription) { $subscription->stripe_status = 'active'; $subscription->save(); } CreateActivity::for($user, 'Purchased', $plan->name . ' Plan'); EmailPaymentConfirmation::create($user, $plan)->send(); } } catch (Exception $th) { } } } public static function objectToArray($request) { if (is_object($request)) { if (property_exists($request, 'id')) { $request = json_decode(json_encode($request), true); } } return $request; } public static function gatewayDefinitionArray(): array { return [ 'code' => 'coingate', 'title' => 'Coingate', 'link' => 'https://coingate.com/', 'active' => 0, // if user activated this gateway - dynamically filled in main page 'available' => 1, // if gateway is available to use 'img' => '/assets/img/payments/coingate.svg', 'whiteLogo' => 0, // if gateway logo is white 'mode' => 1, // Option in settings - Automatically set according to the "Development" mode. "Development" ? sandbox : live (PAYPAL - 1) 'sandbox_client_id' => 0, // Option in settings 0-Hidden 1-Visible 'sandbox_client_secret' => 1, // Option in settings 'sandbox_app_id' => 0, // Option in settings 'live_client_id' => 0, // Option in settings 'live_client_secret' => 1, // Option in settings 'live_app_id' => 0, // Option in settings 'currency' => 0, // Option in settings 'currency_locale' => 0, // Option in settings 'base_url' => 0, // Option in settings 'sandbox_url' => 0, // Option in settings 'locale' => 0, // Option in settings 'validate_ssl' => 0, // Option in settings 'logger' => 0, // Option in settings 'notify_url' => 0, // Gateway notification url at our side 'webhook_secret' => 0, // Option in settings 'tax' => 1, // Option in settings 'bank_account_details' => 0, 'bank_account_other' => 0, ]; } }