feature/pubsub #7

Merged
mats merged 67 commits from feature/pubsub into main 2025-11-28 17:45:22 +01:00
7 changed files with 1678 additions and 5 deletions
Showing only changes of commit c3d8773c2e - Show all commits

View File

@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"git.k6n.net/go-cart-actor/pkg/cart" "git.k6n.net/go-cart-actor/pkg/cart"
"github.com/adyen/adyen-go-api-library/v14/src/checkout"
"github.com/adyen/adyen-go-api-library/v14/src/common"
) )
// CheckoutMeta carries the external / URL metadata required to build a // CheckoutMeta carries the external / URL metadata required to build a
@@ -118,3 +120,61 @@ func BuildCheckoutOrderPayload(grain *cart.CartGrain, meta *CheckoutMeta) ([]byt
return payload, order, nil return payload, order, nil
} }
func BuildAdyenCheckoutSession(grain *cart.CartGrain, meta *CheckoutMeta) (*checkout.CreateCheckoutSessionRequest, error) {
if grain == nil {
return nil, fmt.Errorf("nil grain")
}
if meta == nil {
return nil, fmt.Errorf("nil checkout meta")
}
currency := meta.Currency
if currency == "" {
currency = "SEK"
}
country := meta.Country
if country == "" {
country = "SE"
}
lineItems := make([]checkout.LineItem, 0, len(grain.Items)+len(grain.Deliveries))
// Item lines
for _, it := range grain.Items {
if it == nil {
continue
}
lineItems = append(lineItems, checkout.LineItem{
Quantity: common.PtrInt64(int64(it.Quantity)),
AmountIncludingTax: common.PtrInt64(it.TotalPrice.IncVat),
Description: common.PtrString(it.Meta.Name),
})
}
// Delivery lines
for _, d := range grain.Deliveries {
if d == nil || d.Price.IncVat <= 0 {
continue
}
lineItems = append(lineItems, checkout.LineItem{
Quantity: common.PtrInt64(1),
AmountIncludingTax: common.PtrInt64(d.Price.IncVat),
Description: common.PtrString("Delivery"),
})
}
return &checkout.CreateCheckoutSessionRequest{
Reference: grain.Id.String(),
Amount: checkout.Amount{
Value: grain.TotalPrice.IncVat,
Currency: currency,
},
CountryCode: common.PtrString(country),
MerchantAccount: "ElgigantenECOM",
Channel: common.PtrString("Web"),
ReturnUrl: meta.Checkout,
LineItems: lineItems,
}, nil
}

View File

@@ -11,6 +11,7 @@ import (
"git.k6n.net/go-cart-actor/pkg/actor" "git.k6n.net/go-cart-actor/pkg/actor"
"git.k6n.net/go-cart-actor/pkg/cart" "git.k6n.net/go-cart-actor/pkg/cart"
"git.k6n.net/go-cart-actor/pkg/messages" "git.k6n.net/go-cart-actor/pkg/messages"
"github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/go-redis-inventory/pkg/inventory"
amqp "github.com/rabbitmq/amqp091-go" amqp "github.com/rabbitmq/amqp091-go"
) )

View File

@@ -19,6 +19,8 @@ import (
"git.k6n.net/go-cart-actor/pkg/promotions" "git.k6n.net/go-cart-actor/pkg/promotions"
"git.k6n.net/go-cart-actor/pkg/proxy" "git.k6n.net/go-cart-actor/pkg/proxy"
"git.k6n.net/go-cart-actor/pkg/voucher" "git.k6n.net/go-cart-actor/pkg/voucher"
"github.com/adyen/adyen-go-api-library/v14/src/adyen"
"github.com/adyen/adyen-go-api-library/v14/src/common"
"github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/go-redis-inventory/pkg/inventory"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
@@ -154,9 +156,14 @@ func main() {
log.Fatalf("Error creating cart pool: %v\n", err) log.Fatalf("Error creating cart pool: %v\n", err)
} }
adyenClient := adyen.NewClient(&common.Config{
ApiKey: os.Getenv("ADYEN_API_KEY"),
Environment: common.TestEnv,
})
klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD")) klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD"))
syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, inventoryService, inventoryReservationService) syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, inventoryService, inventoryReservationService, adyenClient)
app := &App{ app := &App{
pool: pool, pool: pool,

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"os"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@@ -15,6 +16,9 @@ import (
"git.k6n.net/go-cart-actor/pkg/cart" "git.k6n.net/go-cart-actor/pkg/cart"
messages "git.k6n.net/go-cart-actor/pkg/messages" messages "git.k6n.net/go-cart-actor/pkg/messages"
"git.k6n.net/go-cart-actor/pkg/voucher" "git.k6n.net/go-cart-actor/pkg/voucher"
"github.com/adyen/adyen-go-api-library/v14/src/adyen"
"github.com/adyen/adyen-go-api-library/v14/src/hmacvalidator"
"github.com/adyen/adyen-go-api-library/v14/src/webhook"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/go-redis-inventory/pkg/inventory"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@@ -44,17 +48,19 @@ type PoolServer struct {
actor.GrainPool[*cart.CartGrain] actor.GrainPool[*cart.CartGrain]
pod_name string pod_name string
klarnaClient *KlarnaClient klarnaClient *KlarnaClient
adyenClient *adyen.APIClient
inventoryService inventory.InventoryService inventoryService inventory.InventoryService
reservationService inventory.CartReservationService reservationService inventory.CartReservationService
} }
func NewPoolServer(pool actor.GrainPool[*cart.CartGrain], pod_name string, klarnaClient *KlarnaClient, inventoryService inventory.InventoryService, inventoryReservationService inventory.CartReservationService) *PoolServer { func NewPoolServer(pool actor.GrainPool[*cart.CartGrain], pod_name string, klarnaClient *KlarnaClient, inventoryService inventory.InventoryService, inventoryReservationService inventory.CartReservationService, adyenClient *adyen.APIClient) *PoolServer {
srv := &PoolServer{ srv := &PoolServer{
GrainPool: pool, GrainPool: pool,
pod_name: pod_name, pod_name: pod_name,
klarnaClient: klarnaClient, klarnaClient: klarnaClient,
inventoryService: inventoryService, inventoryService: inventoryService,
reservationService: inventoryReservationService, reservationService: inventoryReservationService,
adyenClient: adyenClient,
} }
return srv return srv
@@ -537,8 +543,8 @@ func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request
} }
var ( var (
tracer = otel.Tracer(name) tracer = otel.Tracer(name)
hmacKey = os.Getenv("ADYEN_HMAC")
meter = otel.Meter(name) meter = otel.Meter(name)
logger = otelslog.NewLogger(name) logger = otelslog.NewLogger(name)
proxyCalls metric.Int64Counter proxyCalls metric.Int64Counter
@@ -716,6 +722,35 @@ func (s *PoolServer) CreateCheckoutOrderHandler(w http.ResponseWriter, r *http.R
return s.WriteResult(w, reply) return s.WriteResult(w, reply)
} }
func (s *PoolServer) AdyenSessionHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
grain, err := s.Get(r.Context(), uint64(cartId))
if err != nil {
return err
}
host := getOriginalHost(r)
country := getCountryFromHost(host)
meta := &CheckoutMeta{
Terms: fmt.Sprintf("https://%s/terms", host),
Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", host),
Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", host),
Country: country,
Currency: getCurrency(country),
Locale: getLocale(country),
}
sessionData, err := BuildAdyenCheckoutSession(grain, meta)
if err != nil {
return err
}
service := s.adyenClient.Checkout()
req := service.PaymentsApi.SessionsInput().CreateCheckoutSessionRequest(*sessionData)
res, _, err := service.PaymentsApi.Sessions(r.Context(), req)
if err != nil {
return err
}
return s.WriteResult(w, res)
}
func (s *PoolServer) Serve(mux *http.ServeMux) { func (s *PoolServer) Serve(mux *http.ServeMux) {
// mux.HandleFunc("OPTIONS /cart", func(w http.ResponseWriter, r *http.Request) { // mux.HandleFunc("OPTIONS /cart", func(w http.ResponseWriter, r *http.Request) {
@@ -739,6 +774,24 @@ func (s *PoolServer) Serve(mux *http.ServeMux) {
})) }))
} }
handleFunc("/adyen_hook", func(w http.ResponseWriter, r *http.Request) {
var notificationRequest webhook.Webhook
if err := json.NewDecoder(r.Body).Decode(&notificationRequest); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
for _, notification := range notificationRequest.GetNotificationItems() {
isValid := hmacvalidator.ValidateHmac(*notification, hmacKey)
if !isValid {
http.Error(w, "Invalid HMAC", http.StatusUnauthorized)
return
} else {
log.Printf("Recieved notification event code: %s, %v", notification.EventCode, notification)
}
}
w.WriteHeader(http.StatusAccepted)
})
handleFunc("GET /cart", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler))) handleFunc("GET /cart", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler)))
handleFunc("GET /cart/add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler))) handleFunc("GET /cart/add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
handleFunc("POST /cart/add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler))) handleFunc("POST /cart/add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler)))
@@ -754,6 +807,7 @@ func (s *PoolServer) Serve(mux *http.ServeMux) {
handleFunc("PUT /cart/subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler))) handleFunc("PUT /cart/subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler)))
handleFunc("DELETE /cart/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler))) handleFunc("DELETE /cart/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
handleFunc("PUT /cart/user", CookieCartIdHandler(s.ProxyHandler(s.SetUserIdHandler))) handleFunc("PUT /cart/user", CookieCartIdHandler(s.ProxyHandler(s.SetUserIdHandler)))
handleFunc("GET /cart/adyen-session", CookieCartIdHandler(s.ProxyHandler(s.AdyenSessionHandler)))
handleFunc("PUT /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.LineItemMarkingHandler))) handleFunc("PUT /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.LineItemMarkingHandler)))
handleFunc("DELETE /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.RemoveLineItemMarkingHandler))) handleFunc("DELETE /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.RemoveLineItemMarkingHandler)))

View File

@@ -82,6 +82,16 @@ spec:
value: "10.10.3.18:6379" value: "10.10.3.18:6379"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
value: "slaskredis" value: "slaskredis"
- name: ADYEN_HMAC
valueFrom:
secretKeyRef:
name: adyen
key: HMAC
- name: ADYEN_API_KEY
valueFrom:
secretKeyRef:
name: adyen
key: API_KEY
- name: KLARNA_API_USERNAME - name: KLARNA_API_USERNAME
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -185,6 +195,16 @@ spec:
value: "service.name=cart,service.version=0.1.2" value: "service.name=cart,service.version=0.1.2"
- name: OTEL_EXPORTER_OTLP_ENDPOINT - name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-debug-service.monitoring:4317" value: "http://otel-debug-service.monitoring:4317"
- name: ADYEN_HMAC
valueFrom:
secretKeyRef:
name: adyen
key: HMAC
- name: ADYEN_API_KEY
valueFrom:
secretKeyRef:
name: adyen
key: API_KEY
- name: KLARNA_API_PASSWORD - name: KLARNA_API_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -290,6 +310,16 @@ spec:
value: "service.name=cart,service.version=0.1.2" value: "service.name=cart,service.version=0.1.2"
- name: OTEL_EXPORTER_OTLP_ENDPOINT - name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-debug-service.monitoring:4317" value: "http://otel-debug-service.monitoring:4317"
- name: ADYEN_HMAC
valueFrom:
secretKeyRef:
name: adyen
key: HMAC
- name: ADYEN_API_KEY
valueFrom:
secretKeyRef:
name: adyen
key: API_KEY
- name: KLARNA_API_USERNAME - name: KLARNA_API_USERNAME
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -472,4 +502,4 @@ spec:
app: cart-inventory app: cart-inventory
ports: ports:
- name: web - name: web
port: 8080 port: 8080

1
go.mod
View File

@@ -31,6 +31,7 @@ require (
require ( require (
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
github.com/adyen/adyen-go-api-library/v14 v14.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect

1520
go.sum

File diff suppressed because it is too large Load Diff