Skip to content

Commit

Permalink
fix: use proper expiry time when fixing up order while getting
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelbrm committed Jul 23, 2024
1 parent 6598d79 commit 419d218
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 177 deletions.
125 changes: 19 additions & 106 deletions services/skus/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package skus

import (
"context"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"errors"
Expand All @@ -19,7 +18,6 @@ import (
uuid "github.com/satori/go.uuid"
"github.com/stripe/stripe-go/v72/webhook"

"github.com/brave-intl/bat-go/libs/clients/radom"
appctx "github.com/brave-intl/bat-go/libs/context"
"github.com/brave-intl/bat-go/libs/handlers"
"github.com/brave-intl/bat-go/libs/inputs"
Expand Down Expand Up @@ -72,7 +70,7 @@ func Router(
{
corsMwrGet := NewCORSMwr(copts, http.MethodGet)
r.Method(http.MethodOptions, "/{orderID}", metricsMwr("GetOrderOptions", corsMwrGet(nil)))
r.Method(http.MethodGet, "/{orderID}", metricsMwr("GetOrder", corsMwrGet(GetOrder(svc))))
r.Method(http.MethodGet, "/{orderID}", metricsMwr("GetOrder", corsMwrGet(handleGetOrder(svc))))
}

r.Method(
Expand Down Expand Up @@ -370,30 +368,30 @@ func CancelOrder(service *Service) handlers.AppHandler {
})
}

// GetOrder is the handler for getting an order
func GetOrder(service *Service) handlers.AppHandler {
func handleGetOrder(svc *Service) handlers.AppHandler {
return handlers.AppHandler(func(w http.ResponseWriter, r *http.Request) *handlers.AppError {
var orderID = new(inputs.ID)
if err := inputs.DecodeAndValidateString(context.Background(), orderID, chi.URLParam(r, "orderID")); err != nil {
return handlers.ValidationError(
"Error validating request url parameter",
map[string]interface{}{
"orderID": err.Error(),
},
)
}
ctx := r.Context()

order, err := service.GetOrder(*orderID.UUID())
orderID, err := uuid.FromString(chi.URLParamFromCtx(ctx, "orderID"))
if err != nil {
return handlers.WrapError(err, "Error retrieving the order", http.StatusInternalServerError)
return handlers.ValidationError("request", map[string]interface{}{"orderID": err.Error()})
}

status := http.StatusOK
if order == nil {
status = http.StatusNotFound
order, err := svc.getTransformOrder(ctx, orderID)
if err != nil {
switch {
case errors.Is(err, context.Canceled):
return handlers.WrapError(model.ErrSomethingWentWrong, "request has been cancelled", model.StatusClientClosedConn)

case errors.Is(err, model.ErrOrderNotFound):
return handlers.WrapError(err, "order not found", http.StatusNotFound)

default:
return handlers.WrapError(err, "Error retrieving the order", http.StatusInternalServerError)
}
}

return handlers.RenderContent(r.Context(), order, w, status)
return handlers.RenderContent(ctx, order, w, http.StatusOK)
})
}

Expand Down Expand Up @@ -993,7 +991,7 @@ func WebhookRouter(svc *Service) chi.Router {
r := chi.NewRouter()

r.Method(http.MethodPost, "/stripe", middleware.InstrumentHandler("HandleStripeWebhook", handleStripeWebhook(svc)))
r.Method(http.MethodPost, "/radom", middleware.InstrumentHandler("HandleRadomWebhook", HandleRadomWebhook(svc)))
// r.Method(http.MethodPost, "/radom", middleware.InstrumentHandler("HandleRadomWebhook", HandleRadomWebhook(svc)))
r.Method(http.MethodPost, "/android", middleware.InstrumentHandler("handleWebhookPlayStore", handleWebhookPlayStore(svc)))
r.Method(http.MethodPost, "/ios", middleware.InstrumentHandler("handleWebhookAppStore", handleWebhookAppStore(svc)))

Expand Down Expand Up @@ -1183,91 +1181,6 @@ func handleWebhookAppStoreH(w http.ResponseWriter, r *http.Request, svc *Service
return handlers.RenderContent(ctx, struct{}{}, w, http.StatusOK)
}

// HandleRadomWebhook handles Radom checkout session webhooks.
func HandleRadomWebhook(service *Service) handlers.AppHandler {
return func(w http.ResponseWriter, r *http.Request) *handlers.AppError {
ctx := r.Context()

lg := logging.Logger(ctx, "payments").With().Str("func", "HandleRadomWebhook").Logger()

// Get webhook secret.
endpointSecret, err := appctx.GetStringFromContext(ctx, appctx.RadomWebhookSecretCTXKey)
if err != nil {
lg.Error().Err(err).Msg("failed to get radom_webhook_secret from context")
return handlers.WrapError(err, "error getting radom_webhook_secret from context", http.StatusInternalServerError)
}

// Check verification key.
if subtle.ConstantTimeCompare([]byte(r.Header.Get("radom-verification-key")), []byte(endpointSecret)) != 1 {
lg.Error().Err(err).Msg("invalid verification key from webhook")
return handlers.WrapError(err, "invalid verification key", http.StatusBadRequest)
}

req := radom.WebhookRequest{}
if err := requestutils.ReadJSON(ctx, r.Body, &req); err != nil {
lg.Error().Err(err).Msg("failed to read request body")
return handlers.WrapError(err, "error reading request body", http.StatusServiceUnavailable)
}

lg.Debug().Str("event_type", req.EventType).Str("data", fmt.Sprintf("%+v", req)).Msg("webhook event captured")

// Handle only successful payment events.
if req.EventType != "managedRecurringPayment" && req.EventType != "newSubscription" {
return handlers.WrapError(err, "event type not implemented", http.StatusBadRequest)
}

// Lookup the order, the checkout session was created with orderId in metadata.
rawOrderID, err := req.Data.CheckoutSession.Metadata.Get("braveOrderId")
if err != nil || rawOrderID == "" {
return handlers.WrapError(err, "brave metadata not found in webhook", http.StatusBadRequest)
}

orderID, err := uuid.FromString(rawOrderID)
if err != nil {
return handlers.WrapError(err, "invalid braveOrderId in request", http.StatusBadRequest)
}

// Set order id to paid, and update metadata values.
if err := service.Datastore.UpdateOrder(orderID, OrderStatusPaid); err != nil {
lg.Error().Err(err).Msg("failed to update order status")
return handlers.WrapError(err, "error updating order status", http.StatusInternalServerError)
}

if err := service.Datastore.AppendOrderMetadata(
ctx, &orderID, "radomCheckoutSession", req.Data.CheckoutSession.CheckoutSessionID); err != nil {
lg.Error().Err(err).Msg("failed to update order metadata")
return handlers.WrapError(err, "error updating order metadata", http.StatusInternalServerError)
}

if req.EventType == "newSubscription" {

if err := service.Datastore.AppendOrderMetadata(
ctx, &orderID, "subscriptionId", req.EventData.NewSubscription.SubscriptionID); err != nil {
lg.Error().Err(err).Msg("failed to update order metadata")
return handlers.WrapError(err, "error updating order metadata", http.StatusInternalServerError)
}

if err := service.Datastore.AppendOrderMetadata(
ctx, &orderID, "subscriptionContractAddress",
req.EventData.NewSubscription.Subscription.AutomatedEVMSubscription.SubscriptionContractAddress); err != nil {

lg.Error().Err(err).Msg("failed to update order metadata")
return handlers.WrapError(err, "error updating order metadata", http.StatusInternalServerError)
}

}

// Set paymentProcessor to Radom.
if err := service.Datastore.AppendOrderMetadata(ctx, &orderID, "paymentProcessor", model.RadomPaymentMethod); err != nil {
lg.Error().Err(err).Msg("failed to update order to add the payment processor")
return handlers.WrapError(err, "failed to update order to add the payment processor", http.StatusInternalServerError)
}

lg.Debug().Str("orderID", orderID.String()).Msg("order is now paid")
return handlers.RenderContent(ctx, "payment successful", w, http.StatusOK)
}
}

func handleStripeWebhook(svc *Service) handlers.AppHandler {
return func(w http.ResponseWriter, r *http.Request) *handlers.AppError {
ctx := r.Context()
Expand Down
4 changes: 2 additions & 2 deletions services/skus/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (suite *ControllersTestSuite) TestGetOrder() {
req, err := http.NewRequest("GET", "/v1/orders/{orderID}", nil)
suite.Require().NoError(err)

getOrderHandler := GetOrder(suite.service)
getOrderHandler := handleGetOrder(suite.service)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("orderID", order.ID.String())
getReq := req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
Expand Down Expand Up @@ -435,7 +435,7 @@ func (suite *ControllersTestSuite) TestGetMissingOrder() {
req, err := http.NewRequest("GET", "/v1/orders/{orderID}", nil)
suite.Require().NoError(err)

getOrderHandler := GetOrder(suite.service)
getOrderHandler := handleGetOrder(suite.service)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("orderID", "9645ca16-bc93-4e37-8edf-cb35b1763216")
getReq := req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
Expand Down
6 changes: 6 additions & 0 deletions services/skus/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ func (o *Order) StripeSubID() (string, bool) {
return sid, ok
}

func (o *Order) StripeSessID() (string, bool) {
sessID, ok := o.Metadata["stripeCheckoutSessionId"].(string)

return sessID, ok
}

func (o *Order) IsIOS() bool {
pp, ok := o.PaymentProc()
if !ok {
Expand Down
65 changes: 65 additions & 0 deletions services/skus/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,71 @@ func TestOrder_StripeSubID(t *testing.T) {
}
}

func TestOrder_StripeSessID(t *testing.T) {
type tcExpected struct {
val string
ok bool
}

type testCase struct {
name string
given model.Order
exp tcExpected
}

tests := []testCase{
{
name: "no_metadata",
},

{
name: "no_field",
given: model.Order{
Metadata: datastore.Metadata{"key": "value"},
},
},

{
name: "not_string",
given: model.Order{
Metadata: datastore.Metadata{
"stripeCheckoutSessionId": 42,
},
},
},

{
name: "empty_string",
given: model.Order{
Metadata: datastore.Metadata{
"stripeCheckoutSessionId": "",
},
},
exp: tcExpected{ok: true},
},

{
name: "sess_id",
given: model.Order{
Metadata: datastore.Metadata{
"stripeCheckoutSessionId": "sess_id",
},
},
exp: tcExpected{val: "sess_id", ok: true},
},
}

for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
actual, ok := tc.given.StripeSessID()
should.Equal(t, tc.exp.ok, ok)
should.Equal(t, tc.exp.val, actual)
})
}
}

func TestOrder_IsIOS(t *testing.T) {
type testCase struct {
name string
Expand Down
Loading

0 comments on commit 419d218

Please sign in to comment.