Compare commits

..

3 Commits

Author SHA1 Message Date
Seth Hobson
94d1aba17a Add modernized Payment Intents pattern with Payment Element
- Restore Payment Intents flow removed by PR, updated for modern best practices
- Use Payment Element instead of legacy Card Element
- Use stripe.confirmPayment() instead of deprecated confirmCardPayment()
- Use automatic_payment_methods instead of hardcoded payment_method_types
- Split Python/JS into separate fenced code blocks for clarity
- Add guidance on when to use Payment Intents vs Checkout Sessions
- Renumber subsequent patterns (Subscription → 4, Customer Portal → 5)
2026-02-19 13:45:55 -05:00
Seth Hobson
204e8129aa Polish Stripe best practices examples for consistency
- Remove payment_method_types=['card'] from Quick Start (dynamic payment methods)
- Remove unused appearance variable from Pattern 2 JS example
- Fix actions access pattern: destructure before use for consistency
- Add inline comments clarifying sync/async distinction and amount format
- Add ui_mode='embedded' to Embedded checkout bullet for completeness
- Replace payment_method_types with automatic_payment_methods in test example
2026-02-19 13:42:36 -05:00
Sawyer
2b8e3166a1 Update to latest Stripe best practices 2026-02-18 20:38:50 -08:00

View File

@@ -21,19 +21,21 @@ Master Stripe payment processing integration for robust, PCI-compliant payment f
### 1. Payment Flows
**Checkout Session (Hosted)**
**Checkout Sessions**
- Stripe-hosted payment page
- Minimal PCI compliance burden
- Fastest implementation
- Supports one-time and recurring payments
- Recommended for most integrations
- Supports all UI paths:
- Stripe-hosted checkout page
- Embedded checkout form (`ui_mode='embedded'`)
- Custom UI with Elements (Payment Element, Express Checkout Element) using `ui_mode='custom'`
- Provides built-in checkout capabilities (line items, discounts, tax, shipping, address collection, saved payment methods, and checkout lifecycle events)
- Lower integration and maintenance burden than Payment Intents
**Payment Intents (Custom UI)**
**Payment Intents (Bespoke control)**
- Full control over payment UI
- You calculate the final amount with taxes, discounts, subscriptions, and currency conversion yourself.
- More complex implementation and long-term maintenance burden
- Requires Stripe.js for PCI compliance
- More complex implementation
- Better customization options
**Setup Intents (Save Payment Methods)**
@@ -77,7 +79,6 @@ stripe.api_key = "sk_test_..."
# Create a checkout session
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price_data': {
'currency': 'usd',
@@ -109,7 +110,6 @@ def create_checkout_session(amount, currency='usd'):
"""Create a one-time payment checkout session."""
try:
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price_data': {
'currency': currency,
@@ -136,11 +136,76 @@ def create_checkout_session(amount, currency='usd'):
raise
```
### Pattern 2: Custom Payment Intent Flow
### Pattern 2: Checkout Sessions with Payment Element
```python
def create_checkout_session_for_elements(amount, currency='usd'):
"""Create a checkout session configured for Payment Element."""
session = stripe.checkout.Session.create(
mode='payment',
ui_mode='custom',
line_items=[{
'price_data': {
'currency': currency,
'product_data': {'name': 'Purchase'},
'unit_amount': amount,
},
'quantity': 1,
}],
return_url='https://yourdomain.com/complete?session_id={CHECKOUT_SESSION_ID}'
)
return session.client_secret # Send to frontend for stripe.initCheckout()
# Frontend (JavaScript)
"""
const stripe = Stripe('pk_test_...');
// initCheckout() is synchronous; loadActions() is async
const checkout = stripe.initCheckout({clientSecret});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const {actions} = loadActionsResult;
const session = actions.getSession();
const button = document.getElementById('pay-button');
const checkoutContainer = document.getElementById('checkout-container');
const emailInput = document.getElementById('email');
const emailErrors = document.getElementById('email-errors');
const errors = document.getElementById('confirm-errors');
// Display grand total (amount in smallest currency unit, e.g. cents)
checkoutContainer.append(`Total: ${session.total.total.amount}`);
// Mount Payment Element
const paymentElement = checkout.createPaymentElement();
paymentElement.mount('#payment-element');
// Store email for submission
emailInput.addEventListener('blur', () => {
actions.updateEmail(emailInput.value).then((result) => {
if (result.error) emailErrors.textContent = result.error.message;
});
});
// Handle form submission
button.addEventListener('click', () => {
actions.confirm().then((result) => {
if (result.type === 'error') errors.textContent = result.error.message;
});
});
}
"""
```
### Pattern 3: Payment Intents with Payment Element (Bespoke Control)
Use this when you need full control over the payment flow and cannot use Checkout Sessions
(e.g., you have your own tax, discount, or subscription calculation engine).
```python
def create_payment_intent(amount, currency='usd', customer_id=None):
"""Create a payment intent for custom checkout UI."""
"""Create a payment intent for bespoke checkout UI with Payment Element."""
intent = stripe.PaymentIntent.create(
amount=amount,
currency=currency,
@@ -148,40 +213,33 @@ def create_payment_intent(amount, currency='usd', customer_id=None):
automatic_payment_methods={
'enabled': True,
},
metadata={
'integration_check': 'accept_a_payment'
}
)
return intent.client_secret # Send to frontend
# Frontend (JavaScript)
"""
const stripe = Stripe('pk_test_...');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
const {error, paymentIntent} = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: cardElement,
billing_details: {
name: 'Customer Name'
}
}
}
);
if (error) {
// Handle error
} else if (paymentIntent.status === 'succeeded') {
// Payment successful
}
"""
```
### Pattern 3: Subscription Creation
```javascript
// Frontend: Mount Payment Element and confirm via Payment Intents
const stripe = Stripe('pk_test_...');
const elements = stripe.elements({clientSecret});
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
document.getElementById('pay-button').addEventListener('click', async () => {
const {error} = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: 'https://yourdomain.com/complete',
},
});
if (error) {
document.getElementById('errors').textContent = error.message;
}
});
```
### Pattern 4: Subscription Creation
```python
def create_subscription(customer_id, price_id):
@@ -204,7 +262,7 @@ def create_subscription(customer_id, price_id):
raise
```
### Pattern 4: Customer Portal
### Pattern 5: Customer Portal
```python
def create_customer_portal_session(customer_id):
@@ -414,7 +472,7 @@ def test_payment_flow():
amount=1000,
currency='usd',
customer=customer.id,
payment_method_types=['card']
automatic_payment_methods={'enabled': True},
)
# Confirm with test card