backend/.claude/skills/apple-google-pay-stripe/SKILL.md
Add Apple Pay and Google Pay to Stripe checkout. Use when: (1) adding mobile wallet payments, (2) improving mobile conversion, (3) implementing one-tap checkout. Stripe Payment Request Button automatically detects device capabilities and shows Apple Pay (Safari/iOS) or Google Pay (Chrome/Android).
npx skillsauth add Ritenoob/ridedine apple-google-pay-stripeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Users want frictionless mobile checkout with Apple Pay (iOS/Safari) and Google Pay (Android/Chrome). Manual card entry on mobile has high abandonment rates.
Use this skill when:
Key insight: Stripe's Payment Request Button API handles both automatically - no separate integrations needed.
User Device Detection
├─ iOS + Safari → Shows Apple Pay
├─ Android + Chrome → Shows Google Pay
├─ Desktop Chrome → Shows Google Pay
└─ Fallback → Regular card form
Single API handles both wallets + detects availability automatically.
import { PaymentRequestButtonElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { useState, useEffect } from 'react';
export function AppleGooglePayButton({
amount, // in cents
currency = 'usd',
onSuccess,
onError
}: Props) {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
const [canMakePayment, setCanMakePayment] = useState(false);
useEffect(() => {
if (!stripe) return;
// Create payment request
const pr = stripe.paymentRequest({
country: 'US',
currency: currency.toLowerCase(),
total: {
label: 'Order Total',
amount: amount, // in cents
},
requestPayerName: true,
requestPayerEmail: true,
});
// Check if Apple Pay or Google Pay is available
pr.canMakePayment().then(result => {
if (result) {
setPaymentRequest(pr);
setCanMakePayment(true);
}
});
// Handle payment method creation
pr.on('paymentmethod', async (event) => {
try {
// Confirm payment on backend
const response = await fetch('/api/confirm-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method_id: event.paymentMethod.id,
amount,
}),
});
const { clientSecret } = await response.json();
// Confirm payment
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{ payment_method: event.paymentMethod.id },
{ handleActions: false }
);
if (error) {
event.complete('fail');
onError?.(error.message);
} else {
event.complete('success');
onSuccess?.(paymentIntent);
}
} catch (err) {
event.complete('fail');
onError?.(err.message);
}
});
}, [stripe, amount, currency, onSuccess, onError]);
if (!canMakePayment) {
return null; // Hide button if not available
}
return (
<PaymentRequestButtonElement
options={{ paymentRequest }}
className="payment-request-button"
/>
);
}
// API endpoint: /api/confirm-payment
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(req: Request) {
const { payment_method_id, amount } = await req.json();
try {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method: payment_method_id,
confirmation_method: 'manual',
confirm: true,
return_url: `${req.headers.get('origin')}/payment/success`,
});
return Response.json({
clientSecret: paymentIntent.client_secret
});
} catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
}
Show Payment Request Button above card form:
<div className="payment-section">
{/* Apple Pay / Google Pay - shows automatically if available */}
<AppleGooglePayButton
amount={totalCents}
currency="usd"
onSuccess={handleSuccess}
onError={handleError}
/>
{/* Divider */}
{canMakePayment && (
<div className="payment-divider">
<span>Or pay with card</span>
</div>
)}
{/* Traditional card form - always shown as fallback */}
<CardElement />
<button onClick={handleCardPayment}>Pay ${total}</button>
</div>
.payment-request-button {
/* Stripe automatically styles for Apple Pay (black) or Google Pay (white) */
height: 44px;
margin-bottom: 16px;
}
.payment-divider {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
}
.payment-divider::before,
.payment-divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #e0e0e0;
}
.payment-divider span {
padding: 0 10px;
color: #666;
font-size: 14px;
}
| Device/Browser | Shows Apple Pay | Shows Google Pay | Fallback to Card | |---------------|-----------------|------------------|------------------| | iPhone Safari | ✅ Yes | ❌ No | ✅ Yes | | iPhone Chrome | ❌ No | ❌ No | ✅ Yes | | Android Chrome | ❌ No | ✅ Yes | ✅ Yes | | Mac Safari | ✅ Yes | ❌ No | ✅ Yes | | Mac Chrome | ❌ No | ✅ Yes (if signed in) | ✅ Yes | | Windows Chrome | ❌ No | ✅ Yes (if signed in) | ✅ Yes |
Enable Apple Pay:
https://yourdomain.com/.well-known/apple-developer-merchantid-domain-associationEnable Google Pay:
Apple Pay and Google Pay ONLY work on HTTPS domains.
localhost (works without HTTPS){
"dependencies": {
"@stripe/stripe-js": "^2.0.0",
"@stripe/react-stripe-js": "^2.0.0",
"stripe": "^14.0.0"
}
}
Industry benchmarks:
Coinbase case study (Moonshot):
Cause: Domain not verified or wallet not enrolled
Fix:
apple-developer-merchantid-domain-association file is accessibleCause: User not signed into Google account with payment method
Fix:
Cause: Missing return_url or invalid amount
Fix:
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method: payment_method_id,
confirmation_method: 'manual',
confirm: true,
return_url: `${origin}/payment/success`, // REQUIRED
});
Files to modify:
apps/web/components/StripePaymentForm.tsx
PaymentRequestButtonElement above card formcanMakePayment()apps/web/app/checkout/page.tsx
<AppleGooglePayButton> componentBackend (no changes needed)
create_checkout_session Edge Function works as-isEstimated implementation time: 30-45 minutes
development
Integrate Coinbase crypto payments into payment systems. Use when: (1) adding crypto payment support, (2) building onchain features, (3) implementing wallet functionality. Covers Coinbase Commerce (payment processor) vs CDP (developer platform), Server Wallets, Embedded Wallets, and multi-network support.
development
Master Vercel deployment for RidenDine web and admin Next.js apps. Use when: (1) deploying to production, (2) configuring environment variables, (3) setting up preview deployments, (4) debugging build failures, (5) configuring domains, (6) seeing "No Next.js version detected" error in Vercel builds, (7) setting up monorepo with separate projects on free tier. Key insight: Vercel monorepos require Root Directory configuration via dashboard (not vercel.json), GitHub integration auto-detects monorepo structure, free tier allows multiple projects.
development
Master Supabase Row Level Security (RLS) for RidenDine. Use when: (1) adding new tables, (2) modifying RLS policies, (3) debugging access control issues, (4) role-based data access. Key insight: All tables use RLS with role-based policies from profiles.role column.
tools
Master Supabase database migrations for RidenDine. Use when: (1) creating new migrations, (2) modifying schema, (3) adding RLS policies, (4) rolling back changes, (5) deploying to production. Key insight: Migrations are SQL files in backend/supabase/migrations/ executed in lexicographical order. Use supabase CLI to generate timestamps and apply migrations.