move more code
This commit is contained in:
@@ -7,22 +7,17 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.k6n.net/go-cart-actor/pkg/actor"
|
||||
"git.k6n.net/go-cart-actor/pkg/cart"
|
||||
"git.k6n.net/go-cart-actor/pkg/checkout"
|
||||
"git.k6n.net/go-cart-actor/pkg/proxy"
|
||||
messages "git.k6n.net/go-cart-actor/proto/checkout"
|
||||
|
||||
adyen "github.com/adyen/adyen-go-api-library/v21/src/adyen"
|
||||
adyenCheckout "github.com/adyen/adyen-go-api-library/v21/src/checkout"
|
||||
"github.com/adyen/adyen-go-api-library/v21/src/common"
|
||||
"github.com/adyen/adyen-go-api-library/v21/src/hmacvalidator"
|
||||
"github.com/adyen/adyen-go-api-library/v21/src/webhook"
|
||||
"github.com/google/uuid"
|
||||
"github.com/matst80/go-redis-inventory/pkg/inventory"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
@@ -50,10 +45,11 @@ var (
|
||||
|
||||
type CheckoutPoolServer struct {
|
||||
actor.GrainPool[*checkout.CheckoutGrain]
|
||||
pod_name string
|
||||
klarnaClient *KlarnaClient
|
||||
adyenClient *adyen.APIClient
|
||||
cartClient *CartClient
|
||||
pod_name string
|
||||
klarnaClient *KlarnaClient
|
||||
adyenClient *adyen.APIClient
|
||||
cartClient *CartClient
|
||||
inventoryService *inventory.RedisInventoryService
|
||||
}
|
||||
|
||||
func NewCheckoutPoolServer(pool actor.GrainPool[*checkout.CheckoutGrain], pod_name string, klarnaClient *KlarnaClient, cartClient *CartClient, adyenClient *adyen.APIClient) *CheckoutPoolServer {
|
||||
@@ -156,56 +152,6 @@ func (s *CheckoutPoolServer) CheckoutHandler(fn func(order *CheckoutOrder, w htt
|
||||
}))
|
||||
}
|
||||
|
||||
func CheckoutIdHandler(fn func(checkoutId checkout.CheckoutId, w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var id checkout.CheckoutId
|
||||
raw := r.PathValue("id")
|
||||
if raw == "" {
|
||||
id = checkout.CheckoutId(cart.MustNewCartId())
|
||||
w.Header().Set("Set-Checkout-Id", id.String())
|
||||
} else {
|
||||
if parsedId, ok := cart.ParseCartId(raw); !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("checkout id is invalid"))
|
||||
return
|
||||
} else {
|
||||
id = checkout.CheckoutId(parsedId)
|
||||
}
|
||||
}
|
||||
|
||||
err := fn(id, w, r)
|
||||
if err != nil {
|
||||
log.Printf("Server error, not remote error: %v\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request, checkoutId checkout.CheckoutId) error) func(checkoutId checkout.CheckoutId, w http.ResponseWriter, r *http.Request) error {
|
||||
return func(checkoutId checkout.CheckoutId, w http.ResponseWriter, r *http.Request) error {
|
||||
if ownerHost, ok := s.OwnerHost(uint64(checkoutId)); ok {
|
||||
ctx, span := tracer.Start(r.Context(), "proxy")
|
||||
defer span.End()
|
||||
span.SetAttributes(attribute.String("checkoutid", checkoutId.String()))
|
||||
hostAttr := attribute.String("other host", ownerHost.Name())
|
||||
span.SetAttributes(hostAttr)
|
||||
logger.InfoContext(ctx, "checkout proxyed", "result", ownerHost.Name())
|
||||
proxyCalls.Add(ctx, 1, metric.WithAttributes(hostAttr))
|
||||
handled, err := ownerHost.Proxy(uint64(checkoutId), w, r, nil)
|
||||
|
||||
grainLookups.Inc()
|
||||
if err == nil && handled {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
_, span := tracer.Start(r.Context(), "own")
|
||||
span.SetAttributes(attribute.String("checkoutid", checkoutId.String()))
|
||||
defer span.End()
|
||||
return fn(w, r, checkoutId)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tracer = otel.Tracer(name)
|
||||
hmacKey = os.Getenv("ADYEN_HMAC")
|
||||
@@ -224,206 +170,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
type SessionRequest struct {
|
||||
SessionId string `json:"sessionId"`
|
||||
SessionResult string `json:"sessionResult"`
|
||||
SessionData string `json:"sessionData,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) AdyenSessionHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
|
||||
|
||||
grain, err := s.Get(r.Context(), uint64(cartId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Method == http.MethodGet {
|
||||
meta := GetCheckoutMetaFromRequest(r)
|
||||
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)
|
||||
// apply checkout started
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.WriteResult(w, res)
|
||||
} else {
|
||||
payload := &SessionRequest{}
|
||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
service := s.adyenClient.Checkout()
|
||||
req := service.PaymentsApi.GetResultOfPaymentSessionInput(payload.SessionId).SessionResult(payload.SessionResult)
|
||||
res, _, err := service.PaymentsApi.GetResultOfPaymentSession(r.Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.WriteResult(w, res)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var notificationRequest webhook.Webhook
|
||||
service := s.adyenClient.Checkout()
|
||||
if err := json.NewDecoder(r.Body).Decode(¬ificationRequest); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cartHostMap := make(map[actor.Host][]webhook.NotificationItem)
|
||||
for _, notificationItem := range *notificationRequest.NotificationItems {
|
||||
item := notificationItem.NotificationRequestItem
|
||||
log.Printf("Recieved notification event code: %s, %+v", item.EventCode, item)
|
||||
|
||||
isValid := hmacvalidator.ValidateHmac(item, hmacKey)
|
||||
if !isValid {
|
||||
log.Printf("notification hmac not valid %s, %v", item.EventCode, item)
|
||||
http.Error(w, "Invalid HMAC", http.StatusUnauthorized)
|
||||
return
|
||||
} else {
|
||||
switch item.EventCode {
|
||||
case "CAPTURE":
|
||||
log.Printf("Capture status: %v", item.Success)
|
||||
// dataBytes, err := json.Marshal(item)
|
||||
// if err != nil {
|
||||
// log.Printf("error marshaling item: %v", err)
|
||||
// http.Error(w, "Error marshaling item", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
//s.ApplyAnywhere(r.Context(),0, &messages.PaymentEvent{PaymentId: item.PspReference, Success: item.Success, Name: item.EventCode, Data: &pbany.Any{Value: dataBytes}})
|
||||
case "AUTHORISATION":
|
||||
|
||||
cartId, ok := cart.ParseCartId(item.MerchantReference)
|
||||
if !ok {
|
||||
log.Printf("invalid cart id %s", item.MerchantReference)
|
||||
http.Error(w, "Invalid cart id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
//s.Apply()
|
||||
|
||||
if host, ok := s.OwnerHost(uint64(cartId)); ok {
|
||||
cartHostMap[host] = append(cartHostMap[host], notificationItem)
|
||||
continue
|
||||
}
|
||||
|
||||
grain, err := s.Get(r.Context(), uint64(cartId))
|
||||
if err != nil {
|
||||
log.Printf("Error getting cart: %v", err)
|
||||
http.Error(w, "Cart not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
meta := GetCheckoutMetaFromRequest(r)
|
||||
pspReference := item.PspReference
|
||||
uid := uuid.New().String()
|
||||
ref := uuid.New().String()
|
||||
req := service.ModificationsApi.CaptureAuthorisedPaymentInput(pspReference).IdempotencyKey(uid).PaymentCaptureRequest(adyenCheckout.PaymentCaptureRequest{
|
||||
Amount: adyenCheckout.Amount{
|
||||
Currency: meta.Currency,
|
||||
Value: grain.CartTotalPrice.IncVat,
|
||||
},
|
||||
MerchantAccount: "ElgigantenECOM",
|
||||
Reference: &ref,
|
||||
})
|
||||
res, _, err := service.ModificationsApi.CaptureAuthorisedPayment(r.Context(), req)
|
||||
if err != nil {
|
||||
log.Printf("Error capturing payment: %v", err)
|
||||
} else {
|
||||
log.Printf("Payment captured successfully: %+v", res)
|
||||
s.Apply(r.Context(), uint64(cartId), &messages.OrderCreated{
|
||||
OrderId: res.PaymentPspReference,
|
||||
Status: item.EventCode,
|
||||
})
|
||||
}
|
||||
default:
|
||||
log.Printf("Unknown event code: %s", item.EventCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
var failed bool = false
|
||||
var lastMock *proxy.MockResponseWriter
|
||||
for host, items := range cartHostMap {
|
||||
notificationRequest.NotificationItems = &items
|
||||
bodyBytes, err := json.Marshal(notificationRequest)
|
||||
if err != nil {
|
||||
log.Printf("error marshaling notification: %+v", err)
|
||||
continue
|
||||
}
|
||||
customBody := bytes.NewReader(bodyBytes)
|
||||
mockW := proxy.NewMockResponseWriter()
|
||||
handled, err := host.Proxy(0, mockW, r, customBody)
|
||||
if err != nil {
|
||||
log.Printf("proxy failed for %s: %+v", host.Name(), err)
|
||||
failed = true
|
||||
lastMock = mockW
|
||||
} else if handled {
|
||||
log.Printf("notification proxied to %s", host.Name())
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
w.WriteHeader(lastMock.StatusCode)
|
||||
w.Write(lastMock.Body.Bytes())
|
||||
} else {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) AdyenReturnHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("Redirect received")
|
||||
|
||||
service := s.adyenClient.Checkout()
|
||||
|
||||
req := service.PaymentsApi.GetResultOfPaymentSessionInput(r.URL.Query().Get("sessionId"))
|
||||
|
||||
res, httpRes, err := service.PaymentsApi.GetResultOfPaymentSession(r.Context(), req)
|
||||
log.Printf("got payment session %+v", res)
|
||||
|
||||
dreq := service.PaymentsApi.PaymentsDetailsInput()
|
||||
dreq = dreq.PaymentDetailsRequest(adyenCheckout.PaymentDetailsRequest{
|
||||
Details: adyenCheckout.PaymentCompletionDetails{
|
||||
RedirectResult: common.PtrString(r.URL.Query().Get("redirectResult")),
|
||||
Payload: common.PtrString(r.URL.Query().Get("payload")),
|
||||
},
|
||||
})
|
||||
|
||||
dres, httpRes, err := service.PaymentsApi.PaymentsDetails(r.Context(), dreq)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("Payment details response: %+v", dres)
|
||||
|
||||
if !common.IsNil(dres.PspReference) && *dres.PspReference != "" {
|
||||
var redirectURL string
|
||||
// Conditionally handle different result codes for the shopper
|
||||
switch *dres.ResultCode {
|
||||
case "Authorised":
|
||||
redirectURL = "/result/success"
|
||||
case "Pending", "Received":
|
||||
redirectURL = "/result/pending"
|
||||
case "Refused":
|
||||
redirectURL = "/result/failed"
|
||||
default:
|
||||
reason := ""
|
||||
if dres.RefusalReason != nil {
|
||||
reason = *dres.RefusalReason
|
||||
} else {
|
||||
reason = *dres.ResultCode
|
||||
}
|
||||
log.Printf("Payment failed: %s", reason)
|
||||
redirectURL = fmt.Sprintf("/result/error?reason=%s", url.QueryEscape(reason))
|
||||
}
|
||||
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(httpRes.StatusCode)
|
||||
json.NewEncoder(w).Encode(httpRes.Status)
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) Serve(mux *http.ServeMux) {
|
||||
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
|
||||
attr := attribute.String("http.route", pattern)
|
||||
@@ -438,9 +184,63 @@ func (s *CheckoutPoolServer) Serve(mux *http.ServeMux) {
|
||||
handlerFunc(w, r)
|
||||
}))
|
||||
}
|
||||
handleFunc("/payment/adyen/session", s.AdyenSessionHandler)
|
||||
handleFunc("/payment/adyen/push", s.AdyenHookHandler)
|
||||
handleFunc("/payment/adyen/return", s.AdyenReturnHandler)
|
||||
//handleFunc("/payment/adyen/cancel", s.AdyenCancelHandler)
|
||||
|
||||
handleFunc("/adyen_hook", s.AdyenHookHandler)
|
||||
handleFunc("/adyen-return", s.AdyenReturnHandler)
|
||||
handleFunc("/payment/klarna/validate", s.KlarnaValidationHandler)
|
||||
handleFunc("/payment/klarna/notification", s.KlarnaNotificationHandler)
|
||||
|
||||
conn, err := amqp.Dial(amqpUrl)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to RabbitMQ: %v", err)
|
||||
}
|
||||
|
||||
orderHandler := NewAmqpOrderHandler(conn)
|
||||
orderHandler.DefineQueue()
|
||||
|
||||
mux.HandleFunc("GET /checkout", s.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprintf(w, tpl, order.HTMLSnippet)
|
||||
return err
|
||||
}))
|
||||
|
||||
mux.HandleFunc("GET /confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
orderId := r.PathValue("order_id")
|
||||
order, err := s.klarnaClient.GetOrder(r.Context(), orderId)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Apply ConfirmationViewed mutation
|
||||
cartId, ok := cart.ParseCartId(order.MerchantReference1)
|
||||
if ok {
|
||||
a.Apply(r.Context(), uint64(cartId), &messages.ConfirmationViewed{})
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if order.Status == "checkout_complete" {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "cartid",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
Expires: time.Unix(0, 0),
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, tpl, order.HTMLSnippet)
|
||||
})
|
||||
|
||||
handleFunc("GET /checkout", s.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
Reference in New Issue
Block a user