// app/layout.tsx
import { StaySignalWidget } from '@/components/StaySignalWidget'; // Adjust path
export default function RootLayout({ children }: { children: React.ReactNode }) {
const handleComplete = (payload: any) => { console.log('StaySignal Complete:', payload); };
const handleError = (error: any) => { console.error('StaySignal Error:', error); };
return (
<html lang="en">
<body>
{children}
{/* Render StaySignalWidget - siteId prop is only needed if not loading via /api/widget/script/[siteId] */}
<StaySignalWidget onComplete={handleComplete} onError={handleError} />
</body>
</html>
);
}
For the Pages Router, the implementation is similar but you would typically initialize the widget in _app.tsx.
Error Handling
It's recommended to implement proper error handling:
<script>
// Global error handler for widget loading issues
window.onerror = function(msg, url, line, col, error) {
console.error('Widget error:', { msg, url, line, col, error });
return false;
};
// Widget-specific error handling
StaySignal.init({
// site_id is no longer needed here, it's included in the script URL
onError: (error) => {
console.error('Widget error:', error);
// Handle the error appropriately
}
});
</script>
Testing
To test your integration:
Ensure the widget script loads successfully
Check browser console for any errors
Test both class-based and JavaScript-based cancel buttons
Verify the onComplete callback is triggered
Test error scenarios by providing invalid subscription IDs
Finding Stripe Subscription IDs
Staysignal requires a subscription_id to identify which subscription a user is trying to cancel. Here are ways to find and use a user's Stripe subscription ID:
From Your Database
Most applications store subscription IDs in their database after a successful Stripe subscription creation. This is the recommended approach for performance and reliability.
<script>
// Example using Node.js with Express and MongoDB
app.get('/account', async (req, res) => {
try {
// Get user ID from session/JWT
const userId = req.user.id;
// Fetch subscription from your database
const user = await User.findById(userId).populate('subscription');
const subscriptionId = user.subscription?.stripeSubscriptionId;
res.render('account', {
user,
subscriptionId // Pass to your view
});
} catch (error) {
console.error('Error fetching subscription:', error);
res.status(500).send('Server error');
}
});
</script>
<?php
// Example using PHP with Laravel
public function showAccount()
{
$user = Auth::user();
$subscription = $user->subscription; // Assuming relationship is set up
$subscriptionId = $subscription ? $subscription->stripe_subscription_id : null;
return view('account', [
'user' => $user,
'subscriptionId' => $subscriptionId
]);
}
# Example using Ruby on Rails
class AccountController < ApplicationController
def show
@user = current_user
@subscription = @user.subscription
@subscription_id = @subscription&.stripe_subscription_id
end
end
# In your view: account.html.erb
<button class="staysignal-cancel" data-subscription_id="<%= @subscription_id %>">
Cancel Subscription
</button>
# Example using Django
def account_view(request):
user = request.user
subscription = Subscription.objects.filter(user=user).first()
subscription_id = subscription.stripe_subscription_id if subscription else None
return render(request, 'account.html', {
'user': user,
'subscription_id': subscription_id
})
Fetching from Stripe API
If you need to fetch the subscription ID directly from Stripe (not recommended for production):
<script>
// Using Stripe Node.js SDK
const stripe = require('stripe')('sk_test_your_stripe_secret_key');
async function getCustomerSubscriptions(customerId) {
try {
const subscriptions = await stripe.subscriptions.list({
customer: customerId,
status: 'active',
limit: 1
});
if (subscriptions.data.length > 0) {
return subscriptions.data[0].id; // Returns the subscription ID
}
return null;
} catch (error) {
console.error('Error fetching from Stripe:', error);
throw error;
}
}
</script>