Remove prediction API endpoint and integrate into stations search
Consolidate prediction functionality by merging /api/prediction endpoint into /api/stations response. Move prediction logic from PredictionController into StationController, returning prediction data alongside station results. Replace usePrediction composable with unified useStations that returns {stations, meta, prediction}. Remove PredictionRequest, related tests, and unused Vue components (FuelFinderTest, MapTest, RecommendationTest, StationListTest). Add PredictionFull component and UpsellBanner. Extend NationalFuelPredictionService to include weekly_summary (7-day series, yesterday/today averages, cheapest/priciest days) and oil signal from price_predictions table. Update Home.vue to consume prediction from stations response. Add Plan::resolveCadenceForUser helper and configure Cashier to use custom Subscription model.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Jobs\SendPaymentFailedReminderJob;
|
||||
use App\Listeners\HandleStripeWebhook;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\User;
|
||||
use App\Models\UserNotificationPreference;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -121,6 +122,63 @@ it('on invoice.payment_failed sets grace_period_until 5 days out and queues both
|
||||
Queue::assertPushed(SendPaymentFailedReminderJob::class, 2);
|
||||
});
|
||||
|
||||
it('persists current_period_start, current_period_end and stripe_data on subscription.updated', function (): void {
|
||||
$user = User::factory()->create(['stripe_id' => 'cus_period_1']);
|
||||
|
||||
$user->subscriptions()->create([
|
||||
'type' => 'default',
|
||||
'stripe_id' => 'sub_period_1',
|
||||
'stripe_status' => 'active',
|
||||
'stripe_price' => 'price_plus_monthly',
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
$start = 1714377600;
|
||||
$end = 1717056000;
|
||||
|
||||
(new HandleStripeWebhook)->handle(new WebhookReceived([
|
||||
'type' => 'customer.subscription.updated',
|
||||
'data' => ['object' => [
|
||||
'id' => 'sub_period_1',
|
||||
'customer' => 'cus_period_1',
|
||||
'current_period_start' => $start,
|
||||
'current_period_end' => $end,
|
||||
'status' => 'active',
|
||||
]],
|
||||
]));
|
||||
|
||||
$sub = Subscription::where('stripe_id', 'sub_period_1')->first();
|
||||
|
||||
expect($sub->current_period_start->timestamp)->toBe($start);
|
||||
expect($sub->current_period_end->timestamp)->toBe($end);
|
||||
expect($sub->stripe_data)->toMatchArray(['id' => 'sub_period_1', 'status' => 'active']);
|
||||
});
|
||||
|
||||
it('reads current_period_end from items.data[0] when not at the root (newer Stripe API)', function (): void {
|
||||
$user = User::factory()->create(['stripe_id' => 'cus_period_2']);
|
||||
|
||||
$user->subscriptions()->create([
|
||||
'type' => 'default',
|
||||
'stripe_id' => 'sub_period_2',
|
||||
'stripe_status' => 'active',
|
||||
'stripe_price' => 'price_plus_monthly',
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
$end = 1719648000;
|
||||
|
||||
(new HandleStripeWebhook)->handle(new WebhookReceived([
|
||||
'type' => 'customer.subscription.updated',
|
||||
'data' => ['object' => [
|
||||
'id' => 'sub_period_2',
|
||||
'customer' => 'cus_period_2',
|
||||
'items' => ['data' => [['current_period_end' => $end]]],
|
||||
]],
|
||||
]));
|
||||
|
||||
expect(Subscription::where('stripe_id', 'sub_period_2')->value('current_period_end')->timestamp)->toBe($end);
|
||||
});
|
||||
|
||||
it('repeat invoice.payment_failed within grace does not re-dispatch reminders', function (): void {
|
||||
Queue::fake();
|
||||
$existingGrace = now()->addDays(3)->startOfSecond();
|
||||
|
||||
Reference in New Issue
Block a user