From 2b8e3166a1338d07a82432bf8cd196b2b314d256 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Wed, 18 Feb 2026 20:38:50 -0800 Subject: [PATCH 1/7] Update to latest Stripe best practices --- .../skills/stripe-integration/SKILL.md | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index 6d0ef0c..73a775b 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -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 + - 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)** @@ -109,7 +111,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,47 +137,64 @@ def create_checkout_session(amount, currency='usd'): raise ``` -### Pattern 2: Custom Payment Intent Flow +### Pattern 2: Checkout Sessions with Payment Element ```python -def create_payment_intent(amount, currency='usd', customer_id=None): - """Create a payment intent for custom checkout UI.""" - intent = stripe.PaymentIntent.create( - amount=amount, - currency=currency, - customer=customer_id, - automatic_payment_methods={ - 'enabled': True, - }, - metadata={ - 'integration_check': 'accept_a_payment' - } +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 intent.client_secret # Send to frontend + return session.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 appearance = { theme: 'stripe' }; -const {error, paymentIntent} = await stripe.confirmCardPayment( - clientSecret, - { - payment_method: { - card: cardElement, - billing_details: { - name: 'Customer Name' - } - } - } -); +const checkout = stripe.initCheckout({clientSecret}); +const loadActionsResult = await checkout.loadActions(); -if (error) { - // Handle error -} else if (paymentIntent.status === 'succeeded') { - // Payment successful +if (loadActionsResult.type === 'success') { + const session = loadActionsResult.actions.getSession(); + const {actions} = loadActionsResult; + + 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 total to user + 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; + }); + }); } """ ``` From 13c1081312a9d05c658324972e86bcf520d5e6fa Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 09:40:36 -0800 Subject: [PATCH 2/7] Remove PMTs param --- plugins/payment-processing/skills/stripe-integration/SKILL.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index 73a775b..9df84d9 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -79,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', @@ -431,8 +430,7 @@ def test_payment_flow(): intent = stripe.PaymentIntent.create( amount=1000, currency='usd', - customer=customer.id, - payment_method_types=['card'] + customer=customer.id ) # Confirm with test card From 17d4eb1fc11111d49aed8ff7cbb35b35d614db41 Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 09:43:34 -0800 Subject: [PATCH 3/7] set automatic_payment_methods --- plugins/payment-processing/skills/stripe-integration/SKILL.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index 9df84d9..7324809 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -429,6 +429,9 @@ def test_payment_flow(): # Create payment intent intent = stripe.PaymentIntent.create( amount=1000, + automatic_payment_methods={ + 'enabled': True + }, currency='usd', customer=customer.id ) From 967b1f7983f0a1a7ffd92084cd3fd1d965b92107 Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 14:18:55 -0800 Subject: [PATCH 4/7] Use appearance var --- plugins/payment-processing/skills/stripe-integration/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index 7324809..a9910b3 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -161,7 +161,7 @@ def create_checkout_session_for_elements(amount, currency='usd'): const stripe = Stripe('pk_test_...'); const appearance = { theme: 'stripe' }; -const checkout = stripe.initCheckout({clientSecret}); +const checkout = stripe.initCheckout({ clientSecret, elementsOptions: { appearance } }); const loadActionsResult = await checkout.loadActions(); if (loadActionsResult.type === 'success') { From b9a6404352cef3258dcfaeff7883f6dba2d0caa8 Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 14:27:45 -0800 Subject: [PATCH 5/7] Cleanup and comments --- .../skills/stripe-integration/SKILL.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index a9910b3..cdbc339 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -94,7 +94,7 @@ session = stripe.checkout.Session.create( }], mode='subscription', success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}', - cancel_url='https://yourdomain.com/cancel', + cancel_url='https://yourdomain.com/cancel' ) # Redirect user to session.url @@ -161,12 +161,15 @@ def create_checkout_session_for_elements(amount, currency='usd'): const stripe = Stripe('pk_test_...'); const appearance = { theme: 'stripe' }; -const checkout = stripe.initCheckout({ clientSecret, elementsOptions: { appearance } }); +const checkout = stripe.initCheckout({ + clientSecret, + elementsOptions: { appearance } +}); const loadActionsResult = await checkout.loadActions(); if (loadActionsResult.type === 'success') { - const session = loadActionsResult.actions.getSession(); const {actions} = loadActionsResult; + const session = actions.getSession(); const button = document.getElementById('pay-button'); const checkoutContainer = document.getElementById('checkout-container'); @@ -174,7 +177,7 @@ if (loadActionsResult.type === 'success') { const emailErrors = document.getElementById('email-errors'); const errors = document.getElementById('confirm-errors'); - // Display total to user + // Display a formatted string representing the total amount checkoutContainer.append(`Total: ${session.total.total.amount}`); // Mount Payment Element From 9da3e5598efbdef03dfc6b80ab8b4a19976ecb15 Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 14:35:06 -0800 Subject: [PATCH 6/7] EwPI --- .../skills/stripe-integration/SKILL.md | 114 ++++++++++++------ 1 file changed, 79 insertions(+), 35 deletions(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index cdbc339..b668699 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -114,7 +114,7 @@ def create_checkout_session(amount, currency='usd'): 'price_data': { 'currency': currency, 'product_data': { - 'name': 'Purchase', + 'name': 'Blue T-shirt', 'images': ['https://example.com/product.jpg'], }, 'unit_amount': amount, # Amount in cents @@ -136,7 +136,7 @@ def create_checkout_session(amount, currency='usd'): raise ``` -### Pattern 2: Checkout Sessions with Payment Element +### Pattern 2: Elements with Checkout Sessions ```python def create_checkout_session_for_elements(amount, currency='usd'): @@ -147,7 +147,7 @@ def create_checkout_session_for_elements(amount, currency='usd'): line_items=[{ 'price_data': { 'currency': currency, - 'product_data': {'name': 'Purchase'}, + 'product_data': {'name': 'Blue T-shirt'}, 'unit_amount': amount, }, 'quantity': 1, @@ -155,53 +155,93 @@ def create_checkout_session_for_elements(amount, currency='usd'): return_url='https://yourdomain.com/complete?session_id={CHECKOUT_SESSION_ID}' ) return session.client_secret # Send to frontend +``` -# Frontend (JavaScript) -""" -const stripe = Stripe('pk_test_...'); -const appearance = { theme: 'stripe' }; +```javascript +const stripe = Stripe("pk_test_..."); +const appearance = { theme: "stripe" }; const checkout = stripe.initCheckout({ - clientSecret, - elementsOptions: { appearance } + clientSecret, + elementsOptions: { appearance }, }); const loadActionsResult = await checkout.loadActions(); -if (loadActionsResult.type === 'success') { - const {actions} = loadActionsResult; - const session = actions.getSession(); +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'); + 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 a formatted string representing the total amount - checkoutContainer.append(`Total: ${session.total.total.amount}`); + // Display a formatted string representing the total amount + checkoutContainer.append(`Total: ${session.total.total.amount}`); - // Mount Payment Element - const paymentElement = checkout.createPaymentElement(); - paymentElement.mount('#payment-element'); + // 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; - }); + // 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; - }); + // Handle form submission + button.addEventListener("click", () => { + actions.confirm().then((result) => { + if (result.type === "error") errors.textContent = result.error.message; }); + }); } -""" ``` -### Pattern 3: Subscription Creation +### Pattern 3: Elements with Payment Intents + +Pattern 2 (Elements with Checkout Sessions) is Stripe's recommended approach, but you can also use Payment Intents as an alternative. + +```python +def create_payment_intent(amount, currency='usd', customer_id=None): + """Create a payment intent for bespoke checkout UI with Payment Element.""" + intent = stripe.PaymentIntent.create( + amount=amount, + currency=currency, + customer=customer_id, + automatic_payment_methods={ + 'enabled': True, + }, + ) + return intent.client_secret # Send to frontend +``` + +```javascript +// Mount Payment Element and confirm via Payment Intents +const stripe = Stripe("pk_test_..."); +const appearance = { theme: "stripe" }; +const elements = stripe.elements({ appearance, 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): @@ -224,7 +264,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): @@ -477,3 +517,7 @@ def test_payment_flow(): - **Hardcoded Amounts**: Use cents/smallest currency unit - **No Retry Logic**: Implement retries for API calls - **Ignoring Test Mode**: Test all edge cases with test cards + +``` + +``` From 25219b70d3da4bb2b4d826d5ab4f979ed9e1ac5c Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Fri, 20 Feb 2026 14:36:23 -0800 Subject: [PATCH 7/7] Restore metadata --- .../payment-processing/skills/stripe-integration/SKILL.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/payment-processing/skills/stripe-integration/SKILL.md b/plugins/payment-processing/skills/stripe-integration/SKILL.md index b668699..0d651bb 100644 --- a/plugins/payment-processing/skills/stripe-integration/SKILL.md +++ b/plugins/payment-processing/skills/stripe-integration/SKILL.md @@ -214,6 +214,9 @@ 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 ``` @@ -517,7 +520,3 @@ def test_payment_flow(): - **Hardcoded Amounts**: Use cents/smallest currency unit - **No Retry Logic**: Implement retries for API calls - **Ignoring Test Mode**: Test all edge cases with test cards - -``` - -```