Overview
The Payout component enables secure collection of payment methods using Stripe Elements. It supports credit/debit cards and SEPA direct debit, with built-in 3D Secure authentication handling.
A Stripe publishable API key is required for the Payout component to function. Never use your secret key in client-side code.
Prerequisites
1. Install Required Packages
npm install @stripe/react-stripe-js @stripe/stripe-js
2. Get Stripe API Keys
Create Stripe Account
Sign up at stripe.com and complete account verification.
Navigate to API Keys
Go to Developers > API Keys in the Stripe Dashboard.
Copy Publishable Key
Copy your Publishable key (starts with pk_test_ for test mode or pk_live_ for production). The publishable key is safe to expose in client-side code. The secret key (starts with sk_) should never be used in frontend applications.
Configure Webhook (Optional)
For production, set up webhook endpoints to handle payment events on your backend.
Environment Variables
Store your Stripe publishable key securely:
.env.local (Next.js)
.env (Create React App)
.env (Vite)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
Use pk_test_... for development and testing, and pk_live_... for production. Never commit API keys to version control.
Basic Usage
Card Payment Method
import { useRef , useState } from 'react' ;
import { Payout , PayoutRef } from '@adoptaunabuelo/react-components' ;
export default function PaymentForm () {
const payoutRef = useRef < PayoutRef >( null );
const [ isLoading , setIsLoading ] = useState ( false );
const handleSubmit = async () => {
const paymentMethod = await payoutRef . current ?. getPaymentMethod ();
if ( paymentMethod ) {
// Send payment method ID to your backend
console . log ( 'Payment method ID:' , paymentMethod . id );
// Process payment on your backend
}
};
return (
< div >
< Payout
ref = { payoutRef }
stripeKey = { process . env . NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ! }
paymentOption = "card"
design = "primary"
onLoading = { setIsLoading }
onSetupConfirmed = { () => {
console . log ( '3D Secure authentication completed' );
} }
/>
< button onClick = { handleSubmit } disabled = { isLoading } >
{ isLoading ? 'Processing...' : 'Submit Payment' }
</ button >
</ div >
);
}
SEPA Direct Debit
import { useRef } from 'react' ;
import { Payout , PayoutRef } from '@adoptaunabuelo/react-components' ;
export default function SepaForm () {
const payoutRef = useRef < PayoutRef >( null );
const handleSubmit = async () => {
const paymentMethod = await payoutRef . current ?. getPaymentMethod ();
if ( paymentMethod ) {
// Send to backend for SEPA debit setup
console . log ( 'SEPA method:' , paymentMethod );
}
};
return (
< div >
< Payout
ref = { payoutRef }
stripeKey = { process . env . NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ! }
paymentOption = "sepa_debit"
design = "secondary"
userData = { {
email: '[email protected] ' // Required for SEPA
} }
placeholderEmail = "Correo electrónico"
placeholderName = "Nombre del titular"
/>
< button onClick = { handleSubmit } >
Add Bank Account
</ button >
</ div >
);
}
Component Props
Required Props
Your Stripe publishable API key (starts with pk_test_ or pk_live_).
paymentOption
'card' | 'sepa_debit'
required
Payment method type to collect:
"card": Credit/debit card with 3D Secure support
"sepa_debit": SEPA direct debit (European bank accounts)
Optional Props
design
'primary' | 'secondary'
default: "primary"
Visual design variant for the form inputs.
3D Secure confirmation URL from Stripe SetupIntent. When provided, opens authentication modal automatically.
Pre-fill user data. Email is required for SEPA direct debit.
Custom placeholder for cardholder/account holder name input.
Custom placeholder for email input (SEPA only).
Custom CSS styles for the payment form container.
When true, displays error state styling on the form.
onLoading
(loading: boolean) => void
Callback fired when loading state changes (during payment method collection or 3D Secure flow).
Callback fired when 3D Secure authentication completes successfully.
Component Methods
Access component methods via ref:
getPaymentMethod()
Collects and returns the payment method from Stripe.
const paymentMethod = await payoutRef . current ?. getPaymentMethod ();
if ( paymentMethod ) {
console . log ( paymentMethod . id ); // pm_abc123...
console . log ( paymentMethod . type ); // "card" or "sepa_debit"
console . log ( paymentMethod . card ); // Card details (if card)
console . log ( paymentMethod . sepa_debit ); // IBAN details (if SEPA)
}
Returns: Promise<PaymentMethod | undefined>
3D Secure Authentication
The component automatically handles 3D Secure (SCA) authentication:
User Submits Card
User enters card details and you call getPaymentMethod().
Backend Creates SetupIntent
Your backend creates a Stripe SetupIntent and may receive a confirmation URL if 3DS is required. const setupIntent = await stripe . setupIntents . create ({
payment_method: paymentMethod . id ,
confirm: true ,
return_url: 'https://yourapp.com/payment/complete'
});
if ( setupIntent . next_action ?. redirect_to_url ?. url ) {
// 3D Secure required
return { confirmUrl: setupIntent . next_action . redirect_to_url . url };
}
Pass Confirmation URL to Component
Update the component with the confirmation URL: const [ confirmUrl , setConfirmUrl ] = useState < string >();
< Payout
ref = { payoutRef }
stripeKey = { stripeKey }
stripeConfirmUrl = { confirmUrl }
paymentOption = "card"
onSetupConfirmed = { () => {
console . log ( '3D Secure completed!' );
// Finalize payment on backend
} }
/>
User Authenticates
The component opens a modal with the bank’s 3D Secure page. After authentication, onSetupConfirmed is called.
Backend Integration
You’ll need a backend endpoint to handle payment method processing:
server.js (Node.js example)
const stripe = require ( 'stripe' )( process . env . STRIPE_SECRET_KEY );
app . post ( '/api/create-setup-intent' , async ( req , res ) => {
try {
const { paymentMethodId , customerId } = req . body ;
// Attach payment method to customer
await stripe . paymentMethods . attach ( paymentMethodId , {
customer: customerId ,
});
// Create SetupIntent for 3D Secure if needed
const setupIntent = await stripe . setupIntents . create ({
payment_method: paymentMethodId ,
customer: customerId ,
confirm: true ,
return_url: ` ${ process . env . APP_URL } /payment/complete` ,
});
res . json ({
success: true ,
confirmUrl: setupIntent . next_action ?. redirect_to_url ?. url ,
});
} catch ( error ) {
res . status ( 400 ). json ({ error: error . message });
}
});
Complete Example with Backend
import { useRef , useState } from 'react' ;
import { Payout , PayoutRef } from '@adoptaunabuelo/react-components' ;
export default function CompletePaymentFlow () {
const payoutRef = useRef < PayoutRef >( null );
const [ loading , setLoading ] = useState ( false );
const [ confirmUrl , setConfirmUrl ] = useState < string >();
const [ error , setError ] = useState < string >();
const handleSubmit = async () => {
try {
setLoading ( true );
setError ( undefined );
// Step 1: Get payment method from Stripe
const paymentMethod = await payoutRef . current ?. getPaymentMethod ();
if ( ! paymentMethod ) {
throw new Error ( 'Failed to collect payment method' );
}
// Step 2: Send to backend
const response = await fetch ( '/api/create-setup-intent' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
paymentMethodId: paymentMethod . id ,
customerId: 'cus_abc123' , // Your Stripe customer ID
}),
});
const data = await response . json ();
// Step 3: Handle 3D Secure if needed
if ( data . confirmUrl ) {
setConfirmUrl ( data . confirmUrl );
} else {
// Payment method set up successfully
console . log ( 'Payment method added!' );
}
} catch ( err ) {
setError ( err . message );
} finally {
setLoading ( false );
}
};
return (
< div >
< Payout
ref = { payoutRef }
stripeKey = { process . env . NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ! }
stripeConfirmUrl = { confirmUrl }
paymentOption = "card"
design = "primary"
error = { !! error }
onLoading = { setLoading }
onSetupConfirmed = { () => {
console . log ( '3D Secure authentication successful!' );
setConfirmUrl ( undefined );
// Verify on backend that setup is complete
} }
/>
{ error && < p style = { { color: 'red' } } > { error } </ p > }
< button onClick = { handleSubmit } disabled = { loading } >
{ loading ? 'Processing...' : 'Add Payment Method' }
</ button >
</ div >
);
}
Styling
The component uses Stripe Elements with custom styling. Font family is set to Poppins:
< Payout
stripeKey = { stripeKey }
paymentOption = "card"
cardStyle = { {
padding: '20px' ,
border: '1px solid #e0e0e0' ,
borderRadius: '8px' ,
} }
/>
Stripe Elements inherit CSS variables. Customize globally:
:root {
--primary-color : #007bff ;
}
.StripeElement {
padding : 12 px ;
border : 1 px solid #ccc ;
border-radius : 4 px ;
}
.StripeElement--focus {
border-color : var ( --primary-color );
}
.StripeElement--invalid {
border-color : #dc3545 ;
}
Testing
Test Cards
Use Stripe’s test cards in development:
Card Number Description 3D Secure 4242 4242 4242 4242Visa - Succeeds No 4000 0027 6000 3184Visa - Requires 3DS Yes 4000 0000 0000 9995Visa - Declined No 5555 5555 5555 4444Mastercard - Succeeds No
Use any future expiration date (e.g., 12/34)
Use any 3-digit CVC (e.g., 123)
Use any postal code
Test SEPA Accounts
IBAN Result DE89370400440532013000Success DE62370400440532013001Fails
Troubleshooting
”Invalid API key” error
Verify you’re using the publishable key (starts with pk_), not the secret key
Check the key matches your environment (test vs. production)
Ensure environment variable is properly loaded
3D Secure modal doesn’t appear
Ensure stripeConfirmUrl prop is set with the confirmation URL from your backend
Check browser console for errors
Verify your backend is properly creating SetupIntents
Email is required for SEPA direct debit. Pass it via the userData prop:
Security Best Practices
Never expose your Stripe secret key in client-side code
Always validate payment methods on your backend before processing
Use HTTPS in production
Enable Stripe’s fraud detection tools (Radar)
Set up webhook signature verification
Next Steps
Payout Component View full component documentation and examples
Stripe Documentation Official Stripe API documentation
Optional Dependencies Learn about other optional peer dependencies