Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion app/Http/Controllers/App/BillingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Inertia\Inertia;
use Inertia\Response;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Throwable;

class BillingController extends Controller
{
Expand Down Expand Up @@ -66,7 +67,7 @@ public function checkout(Request $request, Plan $plan): SymfonyResponse|Redirect
->trialDays(config('cashier.trial_days'));

$checkoutSession = $subscription->checkout([
'success_url' => route('app.billing.processing'),
'success_url' => route('app.billing.processing').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('app.subscribe'),
]);

Expand All @@ -80,12 +81,49 @@ public function processing(Request $request): Response|RedirectResponse
}

$account = $request->user()->account;
$sessionId = $request->query('session_id');

return Inertia::render('billing/Processing', [
'subscriptionActive' => $account && $account->subscribed(Account::SUBSCRIPTION_NAME),
'conversion' => is_string($sessionId) && $sessionId !== '' && $account?->stripe_id
? fn () => $this->buildConversionData($account, $sessionId)
: null,
]);
}

/**
* @return array{value: float, currency: string, transaction_id: string}|null
*/
private function buildConversionData(Account $account, string $sessionId): ?array
{
try {
$session = $account->stripe()->checkout->sessions->retrieve(
$sessionId,
['expand' => ['line_items.data.price']],
);
} catch (Throwable) {
return null;
}

if (data_get($session, 'customer') !== $account->stripe_id) {
return null;
}

$unitAmount = data_get($session, 'line_items.data.0.price.unit_amount');
$currency = data_get($session, 'line_items.data.0.price.currency');
$transactionId = data_get($session, 'id');

if (! is_int($unitAmount) || ! is_string($currency) || ! is_string($transactionId)) {
return null;
}

return [
'value' => $unitAmount / 100,
'currency' => strtoupper($currency),
'transaction_id' => $transactionId,
];
}

public function index(Request $request): Response|RedirectResponse
{
if (config('trypost.self_hosted')) {
Expand Down
15 changes: 14 additions & 1 deletion resources/js/composables/useTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,29 @@ export const useTracking = () => ({
});
},

trackPurchase: (plan: { name: string; interval: string }) => {
trackPurchase: (
plan: { name: string; interval: string },
conversion?: { value: number; currency: string; transaction_id: string } | null,
) => {
captureEvent('checkout.completed', {
plan_name: plan.name,
interval: plan.interval,
...(conversion ? {
conversion_value: conversion.value,
conversion_currency: conversion.currency,
conversion_transaction_id: conversion.transaction_id,
} : {}),
});

push({
event: 'purchase',
plan_name: plan.name,
plan_interval: plan.interval,
...(conversion ? {
conversion_value: conversion.value,
conversion_currency: conversion.currency,
conversion_transaction_id: conversion.transaction_id,
} : {}),
});
},
});
12 changes: 8 additions & 4 deletions resources/js/pages/billing/Processing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Auth } from '@/types';

const props = defineProps<{
subscriptionActive: boolean;
conversion?: { value: number; currency: string; transaction_id: string } | null;
}>();

const page = usePage();
Expand Down Expand Up @@ -40,10 +41,13 @@ watch(

const plan = (page.props.auth as Auth | undefined)?.plan;
if (plan) {
trackPurchase({
name: plan.name,
interval: plan.interval,
});
trackPurchase(
{
name: plan.name,
interval: plan.interval,
},
props.conversion ?? null,
);
}

goHome();
Expand Down
23 changes: 23 additions & 0 deletions tests/Feature/BillingControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,32 @@
$response->assertInertia(fn ($page) => $page
->component('billing/Processing', false)
->has('subscriptionActive')
->where('conversion', null)
);
});

test('billing processing exposes null conversion when session_id query param is missing', function () {
config(['trypost.self_hosted' => false]);

$response = $this->actingAs($this->user)
->get(route('app.billing.processing', ['session_id' => '']));

$response->assertOk();
$response->assertInertia(fn ($page) => $page->where('conversion', null));
});

test('billing processing exposes null conversion when account has no stripe_id', function () {
config(['trypost.self_hosted' => false]);

expect($this->account->stripe_id)->toBeNull();

$response = $this->actingAs($this->user)
->get(route('app.billing.processing', ['session_id' => 'cs_test_123']));

$response->assertOk();
$response->assertInertia(fn ($page) => $page->where('conversion', null));
});

test('shared auth.plan exposes name slug and interval via AuthPlanResource', function () {
config(['trypost.self_hosted' => false]);

Expand Down
Loading