One-Time Payments with Checkout
Create a Checkout Session
import os
import stripe
from flask import redirect, url_for
from flask_login import login_required, current_user
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
@shop_bp.post("/buy/<int:project_id>")
@login_required
def buy(project_id):
project = db.get_or_404(Project, project_id)
session = stripe.checkout.Session.create(
mode="payment",
customer_email=current_user.email,
line_items=[{
"price_data": {
"currency": project.currency,
"unit_amount": to_minor_units(project.price, project.currency),
"product_data": {"name": project.title},
},
"quantity": 1,
}],
metadata={"user_id": str(current_user.id), "project_id": str(project.id)},
success_url=url_for("shop.success", _external=True) + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url=url_for("projects.detail", slug=project.slug, _external=True),
)
return redirect(session.url, code=303)
- Trigger it from a
<form method="post">with a CSRF token, not a link. - Never take the price from the browser; look it up on the server.
- Put identifiers in
metadataso the webhook knows what was bought and by whom.
Amounts in minor units
Stripe amounts are integers in the currency's smallest unit: cents for USD, but JPY has no minor unit.
from decimal import Decimal, ROUND_HALF_UP
ZERO_DECIMAL = {"jpy", "krw", "vnd", "clp"}
def to_minor_units(amount, currency):
value = Decimal(str(amount))
if currency.lower() in ZERO_DECIMAL:
return int(value.quantize(Decimal("1"), rounding=ROUND_HALF_UP))
return int((value * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
int(19.99 * 100) gives 1998 because of floating-point error; rounding with Decimal gives the correct 1999. Check Stripe's documentation for the full list of zero-decimal currencies.
Success page
The success page can show "Thanks! Your payment is processing." and look up the session to display details, but must not grant access by itself: anyone can visit the success URL. Access is granted by the webhook (next module).
Saved prices
For a fixed catalogue, create Products and Prices in the dashboard or API and pass "price": "price_123" instead of price_data. It keeps reporting tidy and lets you change prices without code changes.
Exercise
Add a "Buy source code" button that creates a Checkout Session for a project, with the price looked up server-side, correct minor units, metadata and success/cancel URLs. Pay with a test card.
Check yourself
- Why must the price come from the server?
- What does
int(19.99 * 100)return, and why? - Why is the success page not proof of payment?
आपकी प्रगति केवल इसी ब्राउज़र में सहेजी जाती है।
समीक्षा के लिए भेजें (10 अंक)