This project is a take-home assignment demonstrating a simple e-commerce checkout system integrated with Stripe Payment Element for secure transactions. It enables users to select a book, proceed to checkout, and complete a payment seamlessly using Stripe’s API. The application is built with Flask (Python) and demonstrates how to securely process payments using Stripe's Payment Intents API.
- Secure payment processing using Stripe Payment Intents API
- Checkout and pay for one item via the Stripe Payment Element form
- Display Payment Intent ID & Total Charged Amount upon successful payment
- Installation & Setup
- How It Works
- Project Structure
- Technical Implementation
- Challenges Faced & How They Were Solved
- Future Enhancements
- Python 3.11: Strongly recommended (Python 3.13 has compatibility issues with some dependencies, including older Stripe versions).
- Flask: Lightweight web framework used for rapid prototyping.
- Stripe API Keys (Create an account at Stripe). Once registered, follow this guide to generate your Secret and Publishable Keys.
- Clone the repository
git clone https://github.com/pengbin-zou/pengbin-sa-takehome-project-python.git
cd pengbin-sa-takehome-project-python- Create and activate a virtual environment using Python 3.11:
python3.11 -m venv newvenv
source newvenv/bin/activate # Windows: newvenv\Scripts\activate- Install dependencies
pip install -r requirements.txt- Configure environment variables:
- Rename**
sample.envto.envin the project root. - Populate the file with your Stripe test API keys, for example:
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxx
- Start the Flask server:
flask runNote: If flask run uses Python 3.13 instead of 3.11, force the virtual environment:
python -m flask runThis ensures you’re using Python 3.11 from your virtual environment.
- Open http://127.0.0.1:5000/ in your browser to access the application.
- If the server doesn’t start, ensure the
.envfile is correctly configured and the virtual environment is activated.
- User selects a book, enters your email address and clicks "Pay".
- Backend creates a Stripe Payment Intent via
/create-payment-intent. - Stripe renders a secure payment form using Payment Element.
- User enters dummy payment info (e.g., test card
4242 4242 4242 4242, future expiration like11/30, and any 3-digit CVC like192). - Payment is processed; upon success, the user is redirected to
/success, showing:- Payment Intent ID
- Total Charged Amount
- Verify the payment in Stripe Dashboard.
This is a Flask-based server-side rendered application with client-side Stripe integration. The frontend sends POST requests to the backend (app.py), which interacts with Stripe APIs and returns a clientSecret to initialize the Payment Element.
+---------------------+
| Client (Browser) |
| (HTML + JS) |
| |
| 1. POST /create-payment-intent |
| (checkout.html) |
+---------------------+
|
| HTTP Request
v
+---------------------+
| Flask Backend |
| (app.py) |
| |
| 2. Interact with |
| Stripe Payment Intents API |
| (app.py) |
| 3. Return clientSecret |
+---------------------+
|
| HTTP Response
v
+---------------------+
| Client (Browser) |
| (Payment Element) |
| 4. Initialize Payment |
| Element with |
| clientSecret
| (checkout.html) |
+---------------------+
PengBin SA Take-Home Project/
├── app.py # Main Flask application
├── views # HTML templates
│ ├── index.html # Home page
│ ├── checkout.html # Payment form
│ ├── success.html # Payment confirmation
│ └── layouts/ # Layout templates
│ └── main.html # Base layout
├── public # Static assets (e.g., CSS, JS)
├── sample.env # Sample API keys
├── requirements.txt # Python dependencies
├── README.md # This documentation
- Payment Intents API: Create a payment intent with the amount and currency on the server side (
stripe.PaymentIntent.create). - Payment Element: Render a secure, customizable payment form on the client side (
elements.create("payment")).
@app.route('/create-payment-intent', methods=['POST'])
def create_payment_intent():
data = request.json
amount = data.get("amount")
try:
intent = stripe.PaymentIntent.create(
amount=amount,
currency='usd'
)
return jsonify({'clientSecret': intent.client_secret})
except stripe.error.StripeError as e:
return jsonify({'error': str(e)}), 500Happened at Step: Displaying Total Amount in Checkout Page (checkout.html)
Issue:
- The total amount was displayed as $25.00 instead of $25 in the checkout page, which could confuse users during payment.
- This occurred because Stripe returns the amount in cents (e.g., 2500 for $25), but the JavaScript initially formatted it as a floating-point value instead of an integer.
Cause:
- The frontend and backend both needed to convert the amount from cents to dollars by dividing by 100, but the initial implementation lacked consistent formatting.
Solution:
- Applied
parseInt(amount) / 100in the frontend JavaScript and Flask backend to ensure consistent conversion from cents to dollars. - Added validation in both layers to prevent incorrect formatting, improving reliability and user clarity during checkout.
// Correcting amount formatting in checkout.html
document.getElementById("pay-button-amount").innerText = parseInt(amount) / 100;Happened at Step: Rendering Payment Element in Checkout (checkout.html)
Issue:
- The Payment Element failed to render, leaving users unable to enter payment details and complete purchases.
Cause:
- The
clientSecretwas undefined during mounting because the fetch request to/create-payment-intentcompleted asynchronously after theelements.create("payment")call.
Solution:
- Used
async/awaitto ensure theclientSecretwas fetched before callingelements.create("payment"). - Thoroughly tested the payment flow to confirm the Payment Element rendered reliably, improving the checkout experience.
// Ensure clientSecret is received before initializing Stripe Elements
const paymentIntentResponse = await fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: amount })
});
const paymentIntentData = await paymentIntentResponse.json();
if (!paymentIntentData.clientSecret) {
document.getElementById("payment-message").innerText = "Error: Client Secret not received!";
return;
}
const clientSecret = paymentIntentData.clientSecret;
// Initialize Stripe Elements after receiving clientSecret
const stripe = Stripe("your-publishable-key");
const elements = stripe.elements({ clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");Verification:
- Added a debugging log to check if clientSecret was received before initializing Stripe Elements:
console.log("Received clientSecret:", clientSecret);Happened at Step: Fetching clientSecret from Backend (/create-payment-intent in app.py)
Issue:
- The
clientSecretwasn’t reaching the frontend, causing payment confirmation to fail and preventing customers from completing transactions.
Cause:
- The JSON response from
/create-payment-intentwas missing theclientSecretdue to an error in the Flask route, likely from improper error handling or response formatting.
Solution:
- Ensured
clientSecretwas explicitly included in the JSON response from Flask.
# Corrected Flask route to return clientSecret
@app.route('/create-payment-intent', methods=['POST'])
def create_payment_intent():
data = request.json
amount = data.get("amount")
try:
intent = stripe.PaymentIntent.create(
amount=amount,
currency='usd'
)
return jsonify({'clientSecret': intent.client_secret})
except stripe.error.StripeError as e:
return jsonify({'error': str(e)}), 500Verification:
- Used Stripe’s test card (
4242 4242 4242 4242) to confirm payment worked. - Opened developer tools (Network tab) to inspect API responses and confirm clientSecret was received.
- Webhooks: Add real-time payment status updates for improved reliability and user feedback.
- Refund Functionality: Integrate the Refund API to allow transaction reversals.
- Database Integration: Store transactions for order tracking and receipt generation.
- Multiple Payment Methods: Support Apple Pay, Google Pay, etc., for broader accessibility.
Thank you for reviewing my submission! I’ve aimed to create a clear, functional, and extensible solution that showcases Stripe’s payment capabilities.