Update
This commit is contained in:
205
MEMEAIGEN/STRIPE WEBHOOKS.bru
Normal file
205
MEMEAIGEN/STRIPE WEBHOOKS.bru
Normal file
@@ -0,0 +1,205 @@
|
||||
meta {
|
||||
name: STRIPE WEBHOOKS
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://memeaigen.test/stripe/webhook
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"id": "evt_1Rg71IEEXQJo9EEO4GHqZdRZ",
|
||||
"object": "event",
|
||||
"api_version": "2025-05-28.basil",
|
||||
"created": 1751387215,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "sub_1Rg71FEEXQJo9EEO18sXSEho",
|
||||
"object": "subscription",
|
||||
"application": null,
|
||||
"application_fee_percent": null,
|
||||
"automatic_tax": {
|
||||
"disabled_reason": null,
|
||||
"enabled": false,
|
||||
"liability": null
|
||||
},
|
||||
"billing_cycle_anchor": 1751387213,
|
||||
"billing_cycle_anchor_config": null,
|
||||
"billing_mode": {
|
||||
"type": "classic"
|
||||
},
|
||||
"billing_thresholds": null,
|
||||
"cancel_at": null,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"cancellation_details": {
|
||||
"comment": null,
|
||||
"feedback": null,
|
||||
"reason": null
|
||||
},
|
||||
"collection_method": "charge_automatically",
|
||||
"created": 1751387213,
|
||||
"currency": "usd",
|
||||
"customer": "cus_SbGYl34MpG4nv5",
|
||||
"days_until_due": null,
|
||||
"default_payment_method": "pm_1Rg71EEEXQJo9EEOJWUAU6EQ",
|
||||
"default_source": null,
|
||||
"default_tax_rates": [],
|
||||
"description": null,
|
||||
"discounts": [],
|
||||
"ended_at": null,
|
||||
"invoice_settings": {
|
||||
"account_tax_ids": null,
|
||||
"issuer": {
|
||||
"type": "self"
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_SbJesuW5WgGoZ7",
|
||||
"object": "subscription_item",
|
||||
"billing_thresholds": null,
|
||||
"created": 1751387213,
|
||||
"current_period_end": 1754065613,
|
||||
"current_period_start": 1751387213,
|
||||
"discounts": [],
|
||||
"metadata": {},
|
||||
"plan": {
|
||||
"id": "price_1RfN2VEEXQJo9EEOzjPI2HGt",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"amount": 400,
|
||||
"amount_decimal": "400",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1751210467,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"meter": null,
|
||||
"nickname": null,
|
||||
"product": "prod_SaY8TGjiPi5hWu",
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"price": {
|
||||
"id": "price_1RfN2VEEXQJo9EEOzjPI2HGt",
|
||||
"object": "price",
|
||||
"active": true,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1751210467,
|
||||
"currency": "usd",
|
||||
"custom_unit_amount": null,
|
||||
"livemode": false,
|
||||
"lookup_key": null,
|
||||
"metadata": {},
|
||||
"nickname": null,
|
||||
"product": "prod_SaY8TGjiPi5hWu",
|
||||
"recurring": {
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"meter": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"tax_behavior": "unspecified",
|
||||
"tiers_mode": null,
|
||||
"transform_quantity": null,
|
||||
"type": "recurring",
|
||||
"unit_amount": 400,
|
||||
"unit_amount_decimal": "400"
|
||||
},
|
||||
"quantity": 1,
|
||||
"subscription": "sub_1Rg71FEEXQJo9EEO18sXSEho",
|
||||
"tax_rates": []
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_1Rg71FEEXQJo9EEO18sXSEho"
|
||||
},
|
||||
"latest_invoice": "in_1Rg71FEEXQJo9EEOmxbGjtdH",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"is_on_session_checkout": "true"
|
||||
},
|
||||
"next_pending_invoice_item_invoice": null,
|
||||
"on_behalf_of": null,
|
||||
"pause_collection": null,
|
||||
"payment_settings": {
|
||||
"payment_method_options": {
|
||||
"acss_debit": null,
|
||||
"bancontact": null,
|
||||
"card": {
|
||||
"network": null,
|
||||
"request_three_d_secure": "automatic"
|
||||
},
|
||||
"customer_balance": null,
|
||||
"konbini": null,
|
||||
"sepa_debit": null,
|
||||
"us_bank_account": null
|
||||
},
|
||||
"payment_method_types": null,
|
||||
"save_default_payment_method": "off"
|
||||
},
|
||||
"pending_invoice_item_interval": null,
|
||||
"pending_setup_intent": null,
|
||||
"pending_update": null,
|
||||
"plan": {
|
||||
"id": "price_1RfN2VEEXQJo9EEOzjPI2HGt",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"amount": 400,
|
||||
"amount_decimal": "400",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1751210467,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"meter": null,
|
||||
"nickname": null,
|
||||
"product": "prod_SaY8TGjiPi5hWu",
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 1,
|
||||
"schedule": null,
|
||||
"start_date": 1751387213,
|
||||
"status": "active",
|
||||
"test_clock": null,
|
||||
"transfer_data": null,
|
||||
"trial_end": null,
|
||||
"trial_settings": {
|
||||
"end_behavior": {
|
||||
"missing_payment_method": "create_invoice"
|
||||
}
|
||||
},
|
||||
"trial_start": null
|
||||
},
|
||||
"previous_attributes": {
|
||||
"default_payment_method": null,
|
||||
"status": "incomplete"
|
||||
}
|
||||
},
|
||||
"livemode": false,
|
||||
"pending_webhooks": 2,
|
||||
"request": {
|
||||
"id": null,
|
||||
"idempotency_key": "e50baede-20b6-4a06-a2c4-15f43ca47dd4"
|
||||
},
|
||||
"type": "customer.subscription.updated"
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,32 @@ public static function getPricingPageOneTime()
|
||||
return $one_time;
|
||||
}
|
||||
|
||||
public static function getSubscriptionPlanByStripePriceID($stripe_price_id)
|
||||
{
|
||||
$plans = self::getSubscriptions('subscription_plans', true, true);
|
||||
|
||||
foreach ($plans as $plan) {
|
||||
|
||||
|
||||
if ($plan['id'] == 'free') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stripe_price_ids = self::getPlanSystemProperty($plan, 'stripe.stripe_price_ids');
|
||||
|
||||
if (is_array($stripe_price_ids)) {
|
||||
if (isset($stripe_price_ids['month']) && in_array($stripe_price_id, $stripe_price_ids['month'])) {
|
||||
return $plan;
|
||||
}
|
||||
if (isset($stripe_price_ids['year']) && in_array($stripe_price_id, $stripe_price_ids['year'])) {
|
||||
return $plan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getPlanSystemProperty($plan, $property)
|
||||
{
|
||||
// Get the environment (test or prod)
|
||||
@@ -50,10 +76,10 @@ public static function getPlanSystemProperty($plan, $property)
|
||||
// Inject environment into the path
|
||||
// stripe.product_id.month becomes system.stripe.product_id.{env}.month
|
||||
array_splice($propertyParts, 2, 0, $environment);
|
||||
$fullPath = 'system.'.implode('.', $propertyParts);
|
||||
$fullPath = 'system.' . implode('.', $propertyParts);
|
||||
} else {
|
||||
// For non-stripe properties, just prepend 'system.'
|
||||
$fullPath = 'system.'.$property;
|
||||
$fullPath = 'system.' . $property;
|
||||
}
|
||||
|
||||
return data_get($plan, $fullPath);
|
||||
|
||||
89
app/Helpers/FirstParty/Purchase/SubscriptionHelper.php
Normal file
89
app/Helpers/FirstParty/Purchase/SubscriptionHelper.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers\FirstParty\Purchase;
|
||||
|
||||
use App\Helpers\FirstParty\Stripe\StripeHelper;
|
||||
use App\Models\Plan;
|
||||
use App\Models\UserPlan;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Cashier\Events\WebhookReceived;
|
||||
|
||||
class SubscriptionHelper
|
||||
{
|
||||
public static function handleSubscriptionWebhookEvents(WebhookReceived $event)
|
||||
{
|
||||
switch ($event->payload['type']) {
|
||||
case 'customer.subscription.created':
|
||||
case 'customer.subscription.updated':
|
||||
self::handleSubscriptionUpsert($event);
|
||||
break;
|
||||
|
||||
case 'customer.subscription.deleted':
|
||||
self::handleSubscriptionDelete($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static function handleSubscriptionUpsert(WebhookReceived $event)
|
||||
{
|
||||
$object = $event->payload['data']['object'];
|
||||
|
||||
//dump($object);
|
||||
|
||||
$ignore_statuses = ['incomplete_expired', 'incomplete'];
|
||||
|
||||
if (in_array($object['status'], $ignore_statuses)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = StripeHelper::getUserByStripeID($object['customer']);
|
||||
|
||||
if ($user) {
|
||||
foreach ($object['items']['data'] as $line_item) {
|
||||
|
||||
//dump($line_item);
|
||||
|
||||
$stripe_price_id = $line_item['plan']['id'];
|
||||
$current_period_end = $line_item['current_period_end'];
|
||||
|
||||
$cancel_at = null;
|
||||
$canceled_at = null;
|
||||
|
||||
if ($line_item['subscription'] == $object['id']) {
|
||||
$cancel_at = $object['cancel_at'];
|
||||
$canceled_at = $object['canceled_at'];
|
||||
}
|
||||
|
||||
$subscription_config = PurchaseHelper::getSubscriptionPlanByStripePriceID($stripe_price_id);
|
||||
|
||||
$plan = Plan::where('tier', $subscription_config['id'])->first();
|
||||
|
||||
if ($plan) {
|
||||
$user_plan = UserPlan::where('user_id', $user->id)->first();
|
||||
|
||||
if ($user_plan) {
|
||||
$user_plan->update([
|
||||
'plan_id' => $plan->id,
|
||||
'current_period_end' => $current_period_end,
|
||||
'cancel_at' => $cancel_at,
|
||||
'canceled_at' => $canceled_at,
|
||||
]);
|
||||
} else {
|
||||
$user_plan = UserPlan::create([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'current_period_end' => $current_period_end,
|
||||
'cancel_at' => $cancel_at,
|
||||
'canceled_at' => $canceled_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function handleSubscriptionDelete(WebhookReceived $event)
|
||||
{
|
||||
// /
|
||||
}
|
||||
}
|
||||
65
app/Helpers/FirstParty/Purchase/WatermarkUsageHelper.php
Normal file
65
app/Helpers/FirstParty/Purchase/WatermarkUsageHelper.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers\FirstParty\Purchase;
|
||||
|
||||
use App\Helpers\FirstParty\Stripe\StripeHelper;
|
||||
use App\Models\UserUsage;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Laravel\Cashier\Events\WebhookReceived;
|
||||
|
||||
class WatermarkUsageHelper
|
||||
{
|
||||
public static function handleWatermarkUsageWebhookEvents(WebhookReceived $event)
|
||||
{
|
||||
switch ($event->payload['type']) {
|
||||
case 'invoice.paid':
|
||||
self::handleInvoicePaid($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static function handleInvoicePaid(WebhookReceived $event)
|
||||
{
|
||||
$object = $event->payload['data']['object'];
|
||||
|
||||
//dump($object);
|
||||
|
||||
$accept_statuses = ['paid', 'partially_paid'];
|
||||
|
||||
if (!in_array($object['status'], $accept_statuses)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = StripeHelper::getUserByStripeID($object['customer']);
|
||||
|
||||
if ($user) {
|
||||
foreach ($object['lines']['data'] as $line_item) {
|
||||
$stripe_price_id = $line_item['pricing']['price_details']['price'];
|
||||
|
||||
//dd($stripe_price_id);
|
||||
|
||||
$subscription_config = PurchaseHelper::getSubscriptionPlanByStripePriceID($stripe_price_id);
|
||||
|
||||
if ($subscription_config) {
|
||||
|
||||
if ($subscription_config['type'] == 'subscription_plans') {
|
||||
$user_usage = UserUsage::where('user_id', $user->id)->first();
|
||||
|
||||
if ($user_usage) {
|
||||
$user_usage->update([
|
||||
'non_watermark_videos_left' => $subscription_config['system']['non_watermark_videos'],
|
||||
]);
|
||||
} else {
|
||||
$user_usage = UserUsage::create([
|
||||
'user_id' => $user->id,
|
||||
'non_watermark_videos_left' => $subscription_config['system']['non_watermark_videos'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
//dd($subscription_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,13 @@
|
||||
|
||||
namespace App\Helpers\FirstParty\Stripe;
|
||||
|
||||
use App\Models\User;
|
||||
use Laravel\Cashier\Events\WebhookReceived;
|
||||
|
||||
class StripeHelper
|
||||
{
|
||||
public static function handleSubscriptionWebhookEvents(WebhookReceived $event)
|
||||
public static function getUserByStripeID($customer_id)
|
||||
{
|
||||
switch ($event->payload['type']) {
|
||||
case 'customer.subscription.created':
|
||||
case 'customer.subscription.updated':
|
||||
self::handleSubscriptionUpsert($event);
|
||||
break;
|
||||
|
||||
case 'customer.subscription.deleted':
|
||||
self::handleSubscriptionDelete($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static function handleSubscriptionUpsert(WebhookReceived $event)
|
||||
{
|
||||
///
|
||||
}
|
||||
|
||||
private static function handleSubscriptionDelete(WebhookReceived $event)
|
||||
{
|
||||
///
|
||||
return User::where('stripe_id', $customer_id)->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public function create(): Response
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:' . User::class,
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class VerifyEmailController extends Controller
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route(config('platform.general.authed_route_redirect'), absolute: false) . '?verified=1');
|
||||
return redirect()->intended(route(config('platform.general.authed_route_redirect'), absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
@@ -25,6 +25,6 @@ public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
event(new Verified($user));
|
||||
}
|
||||
|
||||
return redirect()->intended(route(config('platform.general.authed_route_redirect'), absolute: false) . '?verified=1');
|
||||
return redirect()->intended(route(config('platform.general.authed_route_redirect'), absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Models\UserPlan;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -64,7 +65,7 @@ public function handleGoogleCallback()
|
||||
return redirect()->intended(route('home'))->with('success', "You're now logged in!");
|
||||
} catch (\Exception $e) {
|
||||
|
||||
throw $e;
|
||||
//throw $e;
|
||||
$error_message = 'Google login failed. Please try again.';
|
||||
if (config('app.debug')) {
|
||||
$error_message = $e->getMessage();
|
||||
@@ -78,10 +79,10 @@ private function setupUser($user)
|
||||
{
|
||||
$user_plan = UserPlan::where('user_id', $user->id)->first();
|
||||
|
||||
if (!$user_plan) {
|
||||
if (! $user_plan) {
|
||||
$user_plan = UserPlan::create([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => 'free',
|
||||
'plan_id' => Plan::where('tier', 'free')->first()->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ public function subscribe(Request $request)
|
||||
|
||||
$payload = [
|
||||
'mode' => 'subscription',
|
||||
'success_url' => route('subscribe.success') . '?' . 'session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('subscribe.cancelled') . '?' . 'session_id={CHECKOUT_SESSION_ID}',
|
||||
'success_url' => route('subscribe.success').'?'.'session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('subscribe.cancelled').'?'.'session_id={CHECKOUT_SESSION_ID}',
|
||||
'line_items' => [[
|
||||
'price' => $price_id,
|
||||
'quantity' => 1,
|
||||
@@ -81,8 +81,8 @@ public function purchase(Request $request)
|
||||
$price_id = $request->input('price_id');
|
||||
|
||||
$payload = [
|
||||
'success_url' => route('subscribe.success') . '?' . 'session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('subscribe.cancelled') . '?' . 'session_id={CHECKOUT_SESSION_ID}',
|
||||
'success_url' => route('subscribe.success').'?'.'session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('subscribe.cancelled').'?'.'session_id={CHECKOUT_SESSION_ID}',
|
||||
];
|
||||
|
||||
$checkout_session = Auth::user()->checkout([$price_id => 1], $payload);
|
||||
@@ -107,7 +107,7 @@ public function purchaseSuccess(Request $request)
|
||||
|
||||
Session::forget('checkout_session_id');
|
||||
|
||||
return redirect()->route('home')->with('success', "Thank you for purchasing! Your purchase should be active momentarily. Please refresh the page if you do not see your plan.");
|
||||
return redirect()->route('home')->with('success', 'Thank you for purchasing! Your purchase should be active momentarily. Please refresh the page if you do not see your plan.');
|
||||
}
|
||||
|
||||
public function purchaseCancelled(Request $request)
|
||||
|
||||
@@ -47,15 +47,15 @@ public function share(Request $request): array
|
||||
'user' => $request->user(),
|
||||
'user_is_admin' => user_is_master_admin($request->user()),
|
||||
],
|
||||
'ziggy' => fn(): array => [
|
||||
'ziggy' => fn (): array => [
|
||||
...(new Ziggy)->toArray(),
|
||||
'location' => $request->url(),
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
'flash' => [
|
||||
'message' => fn() => $request->session()->get('message'),
|
||||
'error' => fn() => $request->session()->get('error'),
|
||||
'success' => fn() => $request->session()->get('success'),
|
||||
'message' => fn () => $request->session()->get('message'),
|
||||
'error' => fn () => $request->session()->get('error'),
|
||||
'success' => fn () => $request->session()->get('success'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Helpers\FirstParty\Stripe\StripeHelper;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
use App\Helpers\FirstParty\Purchase\SubscriptionHelper;
|
||||
use App\Helpers\FirstParty\Purchase\WatermarkUsageHelper;
|
||||
use Laravel\Cashier\Events\WebhookReceived;
|
||||
|
||||
class StripeEventListener
|
||||
@@ -23,6 +21,7 @@ public function __construct()
|
||||
*/
|
||||
public function handle(WebhookReceived $event): void
|
||||
{
|
||||
StripeHelper::handleSubscriptionWebhookEvents($event);
|
||||
SubscriptionHelper::handleSubscriptionWebhookEvents($event);
|
||||
WatermarkUsageHelper::handleWatermarkUsageWebhookEvents($event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,19 @@
|
||||
|
||||
/**
|
||||
* Class Plan
|
||||
*
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $tier
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class Plan extends Model
|
||||
{
|
||||
protected $table = 'plans';
|
||||
protected $table = 'plans';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'tier'
|
||||
];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'tier',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Cashier\Billable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Str;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes, Billable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
/**
|
||||
* Class UserPlan
|
||||
*
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $plan_id
|
||||
@@ -20,26 +20,24 @@
|
||||
* @property Carbon|null $canceled_at
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class UserPlan extends Model
|
||||
{
|
||||
protected $table = 'user_plans';
|
||||
protected $table = 'user_plans';
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'int',
|
||||
'plan_id' => 'int',
|
||||
'current_period_end' => 'datetime',
|
||||
'cancel_at' => 'datetime',
|
||||
'canceled_at' => 'datetime'
|
||||
];
|
||||
protected $casts = [
|
||||
'user_id' => 'int',
|
||||
'plan_id' => 'int',
|
||||
'current_period_end' => 'datetime',
|
||||
'cancel_at' => 'datetime',
|
||||
'canceled_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'plan_id',
|
||||
'current_period_end',
|
||||
'cancel_at',
|
||||
'canceled_at'
|
||||
];
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'plan_id',
|
||||
'current_period_end',
|
||||
'cancel_at',
|
||||
'canceled_at',
|
||||
];
|
||||
}
|
||||
|
||||
36
app/Models/UserUsage.php
Normal file
36
app/Models/UserUsage.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class UserUsage
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $non_watermark_videos_left
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*
|
||||
* @package App\Models
|
||||
*/
|
||||
class UserUsage extends Model
|
||||
{
|
||||
protected $table = 'user_usages';
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'int',
|
||||
'non_watermark_videos_left' => 'int'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'non_watermark_videos_left'
|
||||
];
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__ . '/../routes/web.php',
|
||||
api: __DIR__ . '/../routes/api.php',
|
||||
commands: __DIR__ . '/../routes/console.php',
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
then: function () {
|
||||
if (config('platform.general.enable_test_routes')) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_usages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id');
|
||||
$table->integer('non_watermark_videos_left')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('user_usages');
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class PlanSeeder extends Seeder
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
Event::assertDispatched(Verified::class);
|
||||
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
|
||||
$response->assertRedirect(route(config('platform.general.authed_route_redirect'), absolute: false) . '?verified=1');
|
||||
$response->assertRedirect(route(config('platform.general.authed_route_redirect'), absolute: false).'?verified=1');
|
||||
});
|
||||
|
||||
test('email is not verified with invalid hash', function () {
|
||||
|
||||
Reference in New Issue
Block a user