Skip to content

Commit a67776c

Browse files
committed
Added checkout service
1 parent e6514c7 commit a67776c

File tree

12 files changed

+189
-41
lines changed

12 files changed

+189
-41
lines changed

build/checkout.Dockerfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# docker build -t checkout-service -f build/checkout.Dockerfile .
2+
3+
# Stage 1: Create a self signed SSL certificate
4+
FROM alpine:latest AS certs
5+
WORKDIR /app
6+
RUN apk --no-cache add openssl
7+
8+
# Generate self-signed certificate (server.crt and server.key)
9+
RUN openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
10+
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" \
11+
-keyout server.key -out server.crt
12+
13+
# Stage 2: Build the Node application
14+
FROM node:16
15+
WORKDIR /app
16+
17+
# Copy the certificates from the certs stage
18+
COPY --from=certs /app .
19+
20+
# Download and install dependencies
21+
COPY services/checkout/package*.json ./
22+
RUN npm install
23+
24+
# Copy the rest of the application code
25+
COPY services/checkout/. .
26+
27+
# Expose port 7005 for the HTTPS server
28+
EXPOSE 7005
29+
30+
# Start the server
31+
CMD ["node", "index.js"]

build/newsletter.Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# docker build -t users-service -f build/users.Dockerfile .
1+
# docker build -t newsletter-service -f build/newsletter.Dockerfile .
22

33
# Stage 1: Create a self signed SSL certificate
44
FROM alpine:latest AS certs
@@ -10,7 +10,7 @@ RUN openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
1010
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" \
1111
-keyout server.key -out server.crt
1212

13-
# Stage 2: Build the Go application
13+
# Stage 2: Build the Node application
1414
FROM node:16
1515
WORKDIR /app
1616

docker-compose.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,22 @@ services:
5656
REDIS_HOST: redis
5757
PRODUCTS_SERVICE_API: https://products-service:7002
5858
depends_on:
59+
- products-service
5960
- redis
6061

62+
checkout-service:
63+
build:
64+
dockerfile: build/checkout.Dockerfile
65+
restart: unless-stopped
66+
platform: linux/amd64
67+
command: ["node", "index.js"]
68+
ports:
69+
- "7005:7005"
70+
environment:
71+
CART_SERVICE_API: https://cart-service:7004
72+
depends_on:
73+
- cart-service
74+
6175
postgres:
6276
image: postgres:14
6377
restart: unless-stopped

services/checkout/index.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const express = require('express');
88
const bodyParser = require('body-parser');
99
const fetch = require('node-fetch');
1010

11-
const CART_SERVICE_API = process.env.NEWSLETTER_SERVICE_API || 'https://localhost:7003';
11+
const CART_SERVICE_API = process.env.CART_SERVICE_API || 'https://localhost:7004';
1212

1313
// Initialize the app and port
1414
const app = express();
@@ -27,19 +27,27 @@ app.post('/process', async (req, res) => {
2727
}
2828

2929
// Fetch the cart items from the cart service
30-
const response = await fetch(`${CART_SERVICE_API}/?user_id=${user_id}`,{method: 'GET'});
31-
console.log(response);
32-
33-
// try {
34-
// // Create a new subscriber in the database
35-
// await Subscriber.create({ email });
36-
// return res.status(201).json({ message: 'Subscribed successfully' });
37-
// } catch (error) {
38-
// if (error.name === 'SequelizeUniqueConstraintError') {
39-
// return res.status(201).json({ message: 'Subscribed successfully' });
40-
// }
41-
// return res.status(500).json({ error: 'Failed to subscribe' });
42-
// }
30+
let response = await fetch(`${CART_SERVICE_API}/?user_id=${user_id}`,{method: 'GET'});
31+
if (!response.ok) {
32+
return res.status(500).json({ error: 'Failed to fetch cart items' });
33+
}
34+
const cartStatus = await response.json();
35+
36+
// Process the payment
37+
const body = { customer, address, payment, total: cartStatus.total }
38+
response = await fetch(`https://otterize.com`,{
39+
method: 'POST',
40+
body: JSON.stringify(body),
41+
headers: {'Content-Type': 'application/json'}
42+
});
43+
44+
// Empty the cart after checkout
45+
response = await fetch(`${CART_SERVICE_API}/?user_id=${user_id}`,{method: 'DELETE'});
46+
if (!response.ok) {
47+
return res.status(500).json({ error: 'Failed to empty the cart' });
48+
}
49+
50+
return res.status(200).json({ message: 'Checked out' });
4351
});
4452

4553
// Certificate paths

services/frontend/routes/checkout.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import requests
3-
from fastapi import APIRouter, Form, Depends
3+
from fastapi import APIRouter, Form, Depends, Request
44
from fastapi.responses import HTMLResponse
55
from fastapi.templating import Jinja2Templates
66

@@ -13,18 +13,33 @@
1313
templates = Jinja2Templates(directory="templates")
1414

1515

16-
@router.post("/process", tags=["chekcout"], response_class=HTMLResponse)
16+
@router.post("/process", tags=["checkout"], response_class=HTMLResponse)
1717
async def process_checkout(
18+
request: Request,
1819
email: str = Form(...),
20+
card: str = Form(...),
21+
year: str = Form(...),
22+
month: str = Form(...),
23+
cvv: str = Form(...),
1924
session: SessionData = Depends(get_session)
2025
):
2126
api_resp = requests.post(url=f'{CHECKOUT_SERVICE_API}/process/?user_id={session.email}', verify=False, json={
22-
"email": email
27+
"email": email,
28+
"payment": {
29+
"card": card,
30+
"year": year,
31+
"month": month,
32+
"cvv": cvv
33+
}
2334
})
2435
if api_resp.status_code == 200:
25-
return HTMLResponse(status_code=200, content="Successfully subscribed to newsletter!")
36+
return templates.TemplateResponse(
37+
"partials/checkout_success.html",
38+
{"request": request},
39+
headers={"HX-Trigger": "cartChanged"}
40+
)
2641

2742
return HTMLResponse(
2843
status_code=api_resp.status_code,
29-
content="Could not subscribe. Please try again later."
44+
content="Could not complete checkout. Please try again later."
3045
)

services/frontend/routes/views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ async def get_shop_page(request: Request):
1414

1515

1616
@router.get("/cart", response_class=HTMLResponse, dependencies=[Depends(get_session)])
17-
async def get_cart_page(request: Request):
18-
return templates.TemplateResponse("pages/cart.html", {"request": request})
17+
async def get_cart_page(request: Request, session=Depends(get_session)):
18+
return templates.TemplateResponse("pages/cart.html", {
19+
"request": request,
20+
"email": session.email
21+
})
1922

2023

2124
@router.get("/login", response_class=HTMLResponse)

services/frontend/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from routes.cart import router as cart_router
99
from routes.users import router as users_router
1010
from routes.products import router as products_router
11+
from routes.checkout import router as checkout_router
1112
from routes.newsletter import router as newsletter_router
1213

1314
app = FastAPI()
@@ -36,6 +37,7 @@ async def validation_exception_handler(request, exc):
3637
app.include_router(cart_router, prefix="/api/cart", tags=["cart"])
3738
app.include_router(users_router, prefix="/api/users", tags=["users"])
3839
app.include_router(products_router, prefix="/api/products", tags=["products"])
40+
app.include_router(checkout_router, prefix="/api/checkout", tags=["checkout"])
3941
app.include_router(newsletter_router, prefix="/api/newsletter", tags=["newsletter"])
4042

4143

services/frontend/static/app.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ label span {
4848
label input {
4949
background: unset;
5050
width: 100%;
51-
padding: 12px 20px;
51+
padding: 12px 16px;
5252
border: 1px solid #dcdcdc;
5353
font-size: 14px;
5454
box-sizing: border-box;
5555
border-radius: 4px;
56+
color: var(--text-color);
57+
font-weight: 300;
5658
}
5759

5860
label textarea {

services/frontend/templates/base/app-layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</head>
1919

2020
<body>
21-
<div class="app">
21+
<div class="app" id="#app" hx-ext="response-targets">
2222
{% include "partials/header.html" %}
2323
{% block content %}{% endblock %}
2424
{% include "partials/footer.html" %}

services/frontend/templates/pages/cart.html

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@
88
<h1> Checkout </h1>
99
</div>
1010

11-
<div class="cart">
12-
<form class="shipping">
11+
<div class="cart" id="cart">
12+
<form class="shipping" hx-post="{{ url_for('process_checkout') }}" hx-target="#cart" hx-swap="show:#app:top" hx-indicator="#loader">
1313
<div class="details-block">
1414
<h3> Customer Information </h3>
1515

1616
<label for="email">
1717
<span> Email </span>
18-
<input type="email" id="email" name="email" required>
18+
<input type="email" id="email" name="email" value="{{ email }}" required>
1919
</label>
2020

2121
<div style="display: flex; flex-direction: row; gap: 16px;">
2222
<label for="first_name">
2323
<span> First Name </span>
24-
<input type="text" id="first_name" name="first_name" required>
24+
<input type="text" id="first_name" name="first_name">
2525
</label>
2626
<label for="last_name">
2727
<span> Last Name </span>
28-
<input type="text" id="last_name" name="last_name" required>
28+
<input type="text" id="last_name" name="last_name">
2929
</label>
3030
</div>
3131
</div>
@@ -35,29 +35,29 @@ <h3> Shipping Address </h3>
3535

3636
<label for="company_name">
3737
<span> Company Name </span>
38-
<input type="text" id="company_name" name="company_name">
38+
<input type="text" id="company_name" name="company_name" value="Otterize">
3939
</label>
4040

4141
<div style="display: flex; flex-direction: row; gap: 16px;">
4242
<label for="country">
4343
<span> Country </span>
44-
<input type="text" id="country" name="country" required>
44+
<input type="text" id="country" name="country" value="United States">
4545
</label>
4646

4747
<label for="city">
4848
<span> City </span>
49-
<input type="text" id="city" name="city" required>
49+
<input type="text" id="city" name="city" value="New York" required>
5050
</label>
5151

5252
<label for="zip">
5353
<span> Zip </span>
54-
<input type="text" id="zip" name="zip" required>
54+
<input type="text" id="zip" name="zip" value="10011" required>
5555
</label>
5656
</div>
5757

5858
<label for="address">
5959
<span> Street Address </span>
60-
<input type="text" id="address" name="address" required>
60+
<input type="text" id="address" name="address" value="47 W 13th St, New York, NY 10011, USA" required>
6161
</label>
6262
</div>
6363

@@ -66,28 +66,35 @@ <h3> Payment Method </h3>
6666

6767
<label for="card">
6868
<span> Credit Card Number </span>
69-
<input type="text" id="card" name="card" required>
69+
<input type="text" id="card" name="card" value="4111 1111 1111 1111" required>
7070
</label>
7171

7272
<div style="display: flex; flex-direction: row; gap: 16px;">
7373
<label for="month">
7474
<span> Month </span>
75-
<input type="text" id="month" name="month" required>
75+
<input type="text" id="month" name="month" value="12" required>
7676
</label>
7777

7878
<label for="year">
7979
<span> Year </span>
80-
<input type="text" id="year" name="year" required>
80+
<input type="text" id="year" name="year" value="2030" required>
8181
</label>
8282

83-
<label for="zip">
83+
<label for="cvv">
8484
<span> CVV </span>
85-
<input type="text" id="cvv" name="cvv" required>
85+
<input type="text" id="cvv" name="cvv" value="111" required>
8686
</label>
8787
</div>
8888
</div>
8989

90-
<button> Place order </button>
90+
<button id="loader">
91+
<span> Place order </span>
92+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="shape-rendering: auto;" width="16" height="16">
93+
<g data-idx="1" style="fill: #FFFFFF; stroke: none; transform: none; opacity: 1;">
94+
<circle stroke-dasharray="94.24777960769379 33.41592653589793" r="32" stroke-width="4" stroke="#FFFFFF" fill="none" cy="50" cx="50" data-idx="2" transform="matrix(-0.2486899197101593,0.9685831665992737,-0.9685831665992737,-0.2486899197101593,110.86365509033203,14.005337715148926)" style="opacity: 1;"></circle>
95+
</g>
96+
</svg>
97+
</button>
9198
</form>
9299

93100
<div class="summary" hx-get="{{ url_for('cart_summary') }}" hx-trigger="load, cartChanged from:body"></div>
@@ -151,6 +158,35 @@ <h3> Payment Method </h3>
151158
display: flex;
152159
flex-direction: column;
153160
width: 40%;
161+
height: 50px;
162+
}
163+
164+
#loader {
165+
display: flex;
166+
justify-content: center;
167+
align-items: center;
168+
}
169+
#loader svg {
170+
display: none;
171+
animation-name: spin;
172+
animation-duration: 1000ms;
173+
animation-iteration-count: infinite;
174+
animation-timing-function: linear;
175+
}
176+
#loader.htmx-request svg {
177+
display: block;
178+
}
179+
#loader.htmx-request span {
180+
display: none;
181+
}
182+
183+
@keyframes spin {
184+
from {
185+
transform:rotate(0deg);
186+
}
187+
to {
188+
transform:rotate(360deg);
189+
}
154190
}
155191
</style>
156192

services/frontend/templates/partials/cart_summary.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ <h2> {{ product.title }} </h2>
4343
margin-bottom: 0;
4444
border-bottom: unset;
4545
}
46+
.cart-title a {
47+
height: 16px;
48+
}
4649

4750
.item {
4851
display: flex;

0 commit comments

Comments
 (0)