Skip to content

Commit fa49fb2

Browse files
committed
dockerized payments and tested
1 parent e94979d commit fa49fb2

File tree

17 files changed

+418
-58
lines changed

17 files changed

+418
-58
lines changed

docker-compose.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ services:
6060
"
6161
restart: "no"
6262

63+
payments-migrate:
64+
image: postgres:15
65+
depends_on:
66+
postgres:
67+
condition: service_healthy
68+
volumes:
69+
- ./services/payments/migration:/migrations
70+
entrypoint: >
71+
sh -c "
72+
until pg_isready -h postgres -p 5432 -U postgres; do sleep 1; done &&
73+
psql postgres://postgres:postgres@postgres:5432/sabhyatam?sslmode=disable
74+
-f /migrations/001_create_payments.sql
75+
"
76+
restart: "no"
77+
78+
6379
product:
6480
build: ./services/product
6581
container_name: product
@@ -98,6 +114,25 @@ services:
98114
condition: service_started
99115
ports:
100116
- "8082:8082"
117+
environment:
118+
- INTERNAL_SERVICE_KEY=sabhyatam-internal-2025
119+
120+
payments:
121+
build: ./services/payments
122+
container_name: payments
123+
depends_on:
124+
payments-migrate:
125+
condition: service_completed_successfully
126+
orders:
127+
condition: service_started
128+
environment:
129+
DATABASE_URL: postgres://postgres:postgres@postgres:5432/sabhyatam?sslmode=disable
130+
INTERNAL_SERVICE_KEY: sabhyatam-internal-2025
131+
ORDERS_SVC_BASE: http://orders:8082
132+
ports:
133+
- "8083:8083"
134+
135+
101136

102137
volumes:
103138
pgdata:

services/orders/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ toolchain go1.24.11
66

77
require (
88
github.com/go-chi/chi/v5 v5.2.3 // indirect
9+
github.com/google/uuid v1.6.0 // indirect
910
github.com/jackc/pgpassfile v1.0.0 // indirect
1011
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
1112
github.com/jackc/pgx/v5 v5.7.6 // indirect

services/orders/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
33
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
4+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
5+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
46
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
57
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
68
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=

services/orders/internal/api/handlers.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/devmanishoffl/sabhyatam-orders/internal/model"
1010
"github.com/devmanishoffl/sabhyatam-orders/internal/store"
1111
"github.com/go-chi/chi/v5"
12+
"github.com/google/uuid"
1213
)
1314

1415
type Handler struct {
@@ -17,6 +18,11 @@ type Handler struct {
1718
cartClient *client.CartClient
1819
}
1920

21+
func isValidUUID(id string) bool {
22+
_, err := uuid.Parse(id)
23+
return err == nil
24+
}
25+
2026
func NewHandler(s *store.PGStore, pc *client.ProductClient, cc *client.CartClient) *Handler {
2127
return &Handler{store: s, pclient: pc, cartClient: cc}
2228
}
@@ -108,6 +114,13 @@ func (h *Handler) PrepareOrder(w http.ResponseWriter, r *http.Request) {
108114
return
109115
}
110116

117+
for _, it := range orderItems {
118+
if err := h.pclient.ReserveStock(ctx, it.VariantID, it.Quantity); err != nil {
119+
http.Error(w, "stock reservation failed", http.StatusConflict)
120+
return
121+
}
122+
}
123+
111124
w.Header().Set("Content-Type", "application/json")
112125
json.NewEncoder(w).Encode(map[string]any{
113126
"order_id": orderID,
@@ -163,6 +176,12 @@ func (h *Handler) ConfirmOrder(w http.ResponseWriter, r *http.Request) {
163176
// called ONLY by payments service
164177
func (h *Handler) MarkOrderPaid(w http.ResponseWriter, r *http.Request) {
165178
orderID := chi.URLParam(r, "orderID")
179+
180+
if !isValidUUID(orderID) {
181+
http.Error(w, "invalid order id", http.StatusBadRequest)
182+
return
183+
}
184+
166185
if orderID == "" {
167186
http.Error(w, "order id required", http.StatusBadRequest)
168187
return
@@ -189,7 +208,7 @@ func (h *Handler) MarkOrderPaid(w http.ResponseWriter, r *http.Request) {
189208

190209
// deduct stock
191210
for _, it := range order.Items {
192-
if err := h.pclient.DeductStock(ctx, it.VariantID, it.Quantity); err != nil {
211+
if err := h.pclient.DeductReservedStock(ctx, it.VariantID, it.Quantity); err != nil {
193212
http.Error(w, "stock deduction failed", http.StatusBadGateway)
194213
return
195214
}
@@ -202,3 +221,29 @@ func (h *Handler) MarkOrderPaid(w http.ResponseWriter, r *http.Request) {
202221

203222
w.WriteHeader(http.StatusOK)
204223
}
224+
225+
func (h *Handler) GetOrderInternal(w http.ResponseWriter, r *http.Request) {
226+
orderID := chi.URLParam(r, "orderID")
227+
if orderID == "" {
228+
http.Error(w, "order id required", http.StatusBadRequest)
229+
return
230+
}
231+
232+
if r.Header.Get("X-INTERNAL-KEY") != os.Getenv("INTERNAL_SERVICE_KEY") {
233+
http.Error(w, "unauthorized", http.StatusUnauthorized)
234+
return
235+
}
236+
237+
order, err := h.store.GetOrder(r.Context(), orderID)
238+
if err != nil {
239+
http.Error(w, "order not found", http.StatusNotFound)
240+
return
241+
}
242+
243+
_ = json.NewEncoder(w).Encode(map[string]any{
244+
"order_id": order.ID,
245+
"status": order.Status,
246+
"amount_cents": order.TotalAmountCents,
247+
"currency": order.Currency,
248+
})
249+
}

services/orders/internal/api/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ func RegisterRoutes(r *chi.Mux, h *Handler) {
1010
r.Route("/v1/orders", func(r chi.Router) {
1111
r.Post("/prepare", h.PrepareOrder)
1212
r.Post("/confirm", h.ConfirmOrder)
13+
r.Get("/internal/orders/{orderID}", h.GetOrderInternal)
14+
1315
r.Post("/{orderID}/paid", h.MarkOrderPaid)
1416

1517
})

services/orders/internal/client/product_client.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,35 @@ func (p *ProductClient) ReserveStock(ctx context.Context, variantID string, quan
100100
}
101101
return nil
102102
}
103+
104+
func (p *ProductClient) DeductReservedStock(
105+
ctx context.Context,
106+
variantID string,
107+
quantity int,
108+
) error {
109+
110+
url := fmt.Sprintf("%s/v1/admin/variants/%s/deduct", p.base, variantID)
111+
112+
body := map[string]any{
113+
"quantity": quantity,
114+
}
115+
b, _ := json.Marshal(body)
116+
117+
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
118+
req.Header.Set("Content-Type", "application/json")
119+
120+
// INTERNAL AUTH
121+
req.Header.Set("X-ADMIN-KEY", p.adminKey)
122+
123+
resp, err := p.c.Do(req)
124+
if err != nil {
125+
return err
126+
}
127+
defer resp.Body.Close()
128+
129+
if resp.StatusCode != http.StatusOK {
130+
return fmt.Errorf("deduct reserved stock failed: %d", resp.StatusCode)
131+
}
132+
133+
return nil
134+
}

services/orders/internal/store/pg.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (s *PGStore) CreateDraftOrder(
5858
RETURNING id
5959
`,
6060
userID,
61-
model.StatusDraft,
61+
model.StatusPending,
6262
"INR",
6363
totalCents,
6464
).Scan(&orderID)

services/payments/Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
FROM golang:1.23-alpine AS builder
2+
3+
WORKDIR /app
4+
5+
RUN apk add --no-cache git
6+
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
10+
COPY . .
11+
12+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
13+
go build -o paymentsvc ./cmd/paymentsvc
14+
15+
FROM gcr.io/distroless/base-debian11
16+
17+
WORKDIR /app
18+
19+
COPY --from=builder /app/paymentsvc /app/paymentsvc
20+
21+
EXPOSE 8083
22+
23+
USER nonroot:nonroot
24+
25+
ENTRYPOINT ["/app/paymentsvc"]

services/payments/cmd/paymentsvc/main.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,34 @@ package main
33
import (
44
"log"
55
"net/http"
6+
"os"
67

78
"github.com/devmanishoffl/sabhyatam-payments/internal/api"
9+
"github.com/devmanishoffl/sabhyatam-payments/internal/client"
810
"github.com/devmanishoffl/sabhyatam-payments/internal/gateway"
911
"github.com/devmanishoffl/sabhyatam-payments/internal/store"
1012
"github.com/go-chi/chi/v5"
1113
)
1214

1315
func main() {
14-
store, err := store.NewPG("")
16+
dbURL := os.Getenv("DATABASE_URL")
17+
if dbURL == "" {
18+
log.Fatal("DATABASE_URL not set for payments service")
19+
}
20+
21+
// DB
22+
pgStore, err := store.NewPG(dbURL)
1523
if err != nil {
1624
log.Fatal(err)
1725
}
1826

27+
// Gateway
1928
gw := gateway.NewRazorpay()
20-
handler := api.NewHandler(store, gw)
29+
30+
// Orders client
31+
ordersClient := client.NewOrdersClient()
32+
33+
handler := api.NewHandler(pgStore, gw, ordersClient)
2134

2235
r := chi.NewRouter()
2336
api.RegisterRoutes(r, handler)

0 commit comments

Comments
 (0)