Reputation: 31
I am using Laravel 12.x with Inertia, React, Typescript, WorkOS Auth, and Cashier (Paddle).
With my current implementation I can get the checkout overlay to open and process payment, but in my database I am not storing the transaction & subscription. Cashier does not seem to be aware a transaction occurred and a user is subscribed after checkout. On Paddle's end, everything is as expected when I open the customer portal. I am just failing to process my user's transaction and subscription.
I believe I am incorrectly implementing Cashier by manually calling $user->createAsCustomer();
myself in my controller, rather than creating a checkout with Cashier and letting Cashier handle the record creation. I think I am totally doing this wrong.
Cashier (Paddle) Documentation
How could I fix my implementation and leverage Cashier correctly? I struggle with the documentation because of my React/Inertia stack (rather than blade). I see in the documentation $checkout = $user->checkout('pri_34567')->returnTo(route('dashboard'));
but I couldn't get that working with my front-end.
SubscriptionController.php
class SubscriptionController extends Controller
{
public function store(Request $request)
{
try {
$user = $request->user();
$priceId = config('subscription.paddle_price_id');
$user->createAsCustomer();
$redirectUrl = route('dashboard');
// Return the data for the frontend ... I think this is the wrong approach
return response()->json([
'priceId' => $priceId,
'redirectUrl' => $redirectUrl
]);
} catch (\Exception $e) {
Log::error('CHECKOUT - Subscription error: ' . $e->getMessage());
Log::error($e->getTraceAsString());
return response()->json(['error' => $e->getMessage()], 500);
}
}
}
My react component has a button that onClick
calls this function:
useHandleCheckoutOpen.tsx
import axios from 'axios';
import { type User } from '@/types';
import { router } from '@inertiajs/react';
export function useHandleCheckoutOpen(user: User) {
return async () => {
try {
const response = await axios.post('/subscribe');
if (response.data.error) {
console.error('Server returned an error:', response.data.error);
return;
}
const { priceId, redirectUrl } = response.data;
if (window.Paddle && window.Paddle.Checkout) {
window.Paddle.Checkout.open({
items: [
{
priceId,
quantity: 1,
},
],
settings: {
displayMode: 'overlay',
allowLogout: false,
// successUrl: redirectUrl,
},
customer: {
name: user.name,
email: user.email,
},
});
} else {
console.error('Paddle script not loaded or window.Paddle not defined.');
}
} catch (error: any) {
console.error('Error starting subscription:', error);
if (error.response) {
console.error('Server response:', error.response.data);
}
}
};
}
I have included use Billable
in my User Model. My web.php is:
web.php
Route::middleware([
'auth',
ValidateSessionWithWorkOS::class,
])->group(function () {
Route::post('/paddle/webhook', WebhookController::class);
Route::post('/subscribe', [SubscriptionController::class, 'store'])->name('subscribe');
Route::get('/customer-portal', [CustomerPortalController::class, 'show'])->name('customer-portal');
});
I have tried to implement what the Cashier (paddle) documentation references, but because the examples are in blade I am not sure how to relate that to my react front-end. Most examples I can find outside of the documentation are not up to date and refer to old methods that Cashier no longer supports like ->create()
The docs say
use Illuminate\Http\Request;
Route::get('/subscribe', function (Request $request) {
$checkout = $request->user()->checkout('price_basic_monthly')
->returnTo(route('dashboard'));
return view('subscribe', ['checkout' => $checkout]);
})->name('subscribe');
with the blade component:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
Upvotes: 1
Views: 30
Reputation: 31
Cashier automatically listens/processes webhooks from Paddle if configured correctly with the .env
variables. By adding the webhook route to the web.php
file, I was overriding the default behavior of Cashier because this was taking precedence:
Route::post('/paddle/webhook', WebhookController::class);
The solution would be to remove this line so Cashier can process webhooks and create the Transaction & Subscription records when the webhooks are received. Defining that route in web.php
is not necessary.
Upvotes: 2