Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production 2024-09-23_01 #2660

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions libs/wallet/provider/uphold/uphold.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ const (

const (
// The Intermediate Certificates
sandboxFingerprint = "O6hzXihLcuumaJVvdUAVqaeMJx6L5DQea72XQjqduX4="
prodFingerprint = "O6hzXihLcuumaJVvdUAVqaeMJx6L5DQea72XQjqduX4="
sandboxFingerprint = "6w/CV0c2kUVhqqT4obGFcgiWiM34O5NUrXwHZp7MUpI="
prodFingerprint = "6w/CV0c2kUVhqqT4obGFcgiWiM34O5NUrXwHZp7MUpI="
)

var (
Expand Down
11 changes: 11 additions & 0 deletions services/grant/cmd/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,17 @@ func setupRouter(ctx context.Context, logger *zerolog.Logger) (context.Context,
)
}

corsMwrDelete := skus.NewCORSMwr(corsOpts, http.MethodDelete)

subr.Method(
http.MethodDelete,
"/{orderID}",
middleware.InstrumentHandler(
"CancelOrderNew",
corsMwrDelete(authMwr(handlers.AppHandler(orderh.Cancel))),
),
)

r.Mount("/v1/orders-new", subr)
}

Expand Down
2 changes: 1 addition & 1 deletion services/skus/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ func CancelOrder(service *Service) handlers.AppHandler {
return handlers.WrapError(err, "Error validating auth merchant and caveats", http.StatusForbidden)
}

if err := service.CancelOrder(oid); err != nil {
if err := service.CancelOrderLegacy(oid); err != nil {
return handlers.WrapError(err, "Error retrieving the order", http.StatusInternalServerError)
}

Expand Down
39 changes: 39 additions & 0 deletions services/skus/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1899,6 +1899,45 @@ func (suite *ControllersTestSuite) TestWebhook_Radom() {
suite.Equal(model.OrderStatusPaid, order.Status)
}

func (suite *ControllersTestSuite) TestOrder_Cancel() {
ctx := context.Background()

oreq := &model.OrderNew{
Status: OrderStatusPaid,
}

ord, err := suite.service.orderRepo.Create(ctx, suite.service.Datastore.RawDB(), oreq)
suite.Require().NoError(err)

uri := "/v1/orders-new/" + ord.ID.String()

req := httptest.NewRequest(http.MethodDelete, uri, nil)

rw := httptest.NewRecorder()

oh := handler.NewOrder(suite.service)

rtr := chi.NewRouter()
subr := chi.NewRouter()
subr.Method(
http.MethodDelete,
"/{orderID}",
handlers.AppHandler(oh.Cancel),
)

rtr.Mount("/v1/orders-new", subr)

srv := &http.Server{Addr: ":8080", Handler: rtr}
srv.Handler.ServeHTTP(rw, req)

suite.Require().Equal(http.StatusOK, rw.Code)

ord2, err := suite.service.orderRepo.Get(ctx, suite.service.Datastore.RawDB(), ord.ID)
suite.Require().NoError(err)

suite.Equal(model.OrderStatusCanceled, ord2.Status)
}

// ReadSigningOrderRequestMessage reads messages from the unsigned order request topic
func (suite *ControllersTestSuite) ReadSigningOrderRequestMessage(ctx context.Context, topic string) SigningOrderRequest {
kafkaReader, err := kafkautils.NewKafkaReader(ctx, test.RandomString(), topic)
Expand Down
2 changes: 1 addition & 1 deletion services/skus/handler/cred.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (h *Cred) CountBatches(w http.ResponseWriter, r *http.Request) *handlers.Ap
if err != nil {
switch {
case errors.Is(err, context.Canceled):
return handlers.WrapError(err, "cliend ended request", model.StatusClientClosedConn)
return handlers.WrapError(err, "client ended request", model.StatusClientClosedConn)

case errors.Is(err, model.ErrOrderNotFound), errors.Is(err, model.ErrInvalidOrderNoItems), errors.Is(err, model.ErrOrderItemNotFound):
return handlers.WrapError(err, "order not found", http.StatusNotFound)
Expand Down
2 changes: 1 addition & 1 deletion services/skus/handler/cred_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestCred_CountBatches(t *testing.T) {
},
},
exp: tcExpected{
err: handlers.WrapError(context.Canceled, "cliend ended request", model.StatusClientClosedConn),
err: handlers.WrapError(context.Canceled, "client ended request", model.StatusClientClosedConn),
},
},

Expand Down
26 changes: 26 additions & 0 deletions services/skus/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"net/http"

"github.com/asaskevich/govalidator"
"github.com/go-chi/chi"
"github.com/go-playground/validator/v10"
uuid "github.com/satori/go.uuid"

"github.com/brave-intl/bat-go/libs/handlers"
"github.com/brave-intl/bat-go/libs/logging"
Expand All @@ -24,6 +26,7 @@ const (
type orderService interface {
CreateOrderFromRequest(ctx context.Context, req model.CreateOrderRequest) (*model.Order, error)
CreateOrder(ctx context.Context, req *model.CreateOrderRequestNew) (*model.Order, error)
CancelOrder(ctx context.Context, id uuid.UUID) error
}

type Order struct {
Expand Down Expand Up @@ -117,6 +120,29 @@ func (h *Order) CreateNew(w http.ResponseWriter, r *http.Request) *handlers.AppE
return handlers.RenderContent(ctx, result, w, http.StatusCreated)
}

func (h *Order) Cancel(w http.ResponseWriter, r *http.Request) *handlers.AppError {
ctx := r.Context()

orderID, err := uuid.FromString(chi.URLParamFromCtx(ctx, "orderID"))
if err != nil {
return handlers.ValidationError("request", map[string]interface{}{"orderID": model.ErrInvalidUUID})
}

lg := logging.Logger(ctx, "skus").With().Str("func", "CancelOrderNew").Logger()

if err := h.svc.CancelOrder(ctx, orderID); err != nil {
lg.Err(err).Str("order_id", orderID.String()).Msg("failed to cancel order")

if errors.Is(err, context.Canceled) {
return handlers.WrapError(model.ErrSomethingWentWrong, "client ended request", model.StatusClientClosedConn)
}

return handlers.WrapError(model.ErrSomethingWentWrong, "could not cancel order", http.StatusInternalServerError)
}

return handlers.RenderContent(ctx, struct{}{}, w, http.StatusOK)
}

func collectValidationErrors(err error) (map[string]string, bool) {
var verr validator.ValidationErrors
if !errors.As(err, &verr) {
Expand Down
134 changes: 134 additions & 0 deletions services/skus/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
"os"
"testing"

"github.com/go-chi/chi"
"github.com/rs/zerolog"
uuid "github.com/satori/go.uuid"
"github.com/shopspring/decimal"
should "github.com/stretchr/testify/assert"
must "github.com/stretchr/testify/require"
Expand All @@ -30,6 +32,7 @@ func TestMain(m *testing.M) {
type mockOrderService struct {
fnCreateOrderFromRequest func(ctx context.Context, req model.CreateOrderRequest) (*model.Order, error)
fnCreateOrder func(ctx context.Context, req *model.CreateOrderRequestNew) (*model.Order, error)
fnCancelOrder func(ctx context.Context, id uuid.UUID) error
}

func (s *mockOrderService) CreateOrderFromRequest(ctx context.Context, req model.CreateOrderRequest) (*model.Order, error) {
Expand All @@ -48,6 +51,14 @@ func (s *mockOrderService) CreateOrder(ctx context.Context, req *model.CreateOrd
return s.fnCreateOrder(ctx, req)
}

func (s *mockOrderService) CancelOrder(ctx context.Context, id uuid.UUID) error {
if s.fnCancelOrder == nil {
return nil
}

return s.fnCancelOrder(ctx, id)
}

func TestOrder_Create(t *testing.T) {
type tcGiven struct {
svc *mockOrderService
Expand Down Expand Up @@ -504,6 +515,129 @@ func TestOrder_CreateNew(t *testing.T) {
}
}

func TestOrder_Cancel(t *testing.T) {
type tcGiven struct {
ctx context.Context
svc *mockOrderService
oid uuid.UUID
}

type tcExpected struct {
err *handlers.AppError
}

type testCase struct {
name string
given tcGiven
exp tcExpected
}

tests := []testCase{
{
name: "invalid_orderID",
given: tcGiven{
ctx: context.WithValue(context.Background(), chi.RouteCtxKey, &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"orderID"},
Values: []string{"invalid_id"},
},
}),
svc: &mockOrderService{},
oid: uuid.Nil,
},
exp: tcExpected{
err: handlers.ValidationError("request", map[string]interface{}{"orderID": model.ErrInvalidUUID}),
},
},

{
name: "context_cancelled",
given: tcGiven{
ctx: context.WithValue(context.Background(), chi.RouteCtxKey, &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"orderID"},
Values: []string{"facade00-0000-4000-a000-000000000000"},
},
}),
svc: &mockOrderService{
fnCancelOrder: func(ctx context.Context, id uuid.UUID) error {
return context.Canceled
},
},
oid: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
},
exp: tcExpected{
err: handlers.WrapError(model.ErrSomethingWentWrong, "client ended request", model.StatusClientClosedConn),
},
},

{
name: "something_went_wrong",
given: tcGiven{
ctx: context.WithValue(context.Background(), chi.RouteCtxKey, &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"orderID"},
Values: []string{"facade00-0000-4000-a000-000000000000"},
},
}),
svc: &mockOrderService{
fnCancelOrder: func(ctx context.Context, id uuid.UUID) error {
return model.Error("any_error")
},
},
oid: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
},
exp: tcExpected{
err: handlers.WrapError(model.ErrSomethingWentWrong, "could not cancel order", http.StatusInternalServerError),
},
},

{
name: "success",
given: tcGiven{
ctx: context.WithValue(context.Background(), chi.RouteCtxKey, &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"orderID"},
Values: []string{"facade00-0000-4000-a000-000000000000"},
},
}),
svc: &mockOrderService{},
oid: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
},
exp: tcExpected{},
},
}

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

t.Run(tc.name, func(t *testing.T) {
h := handler.NewOrder(tc.given.svc)

uri := "http://localhost/v1/orders-new/" + tc.given.oid.String()
req := httptest.NewRequest(http.MethodDelete, uri, nil)
req = req.WithContext(tc.given.ctx)

rw := httptest.NewRecorder()
rw.Header().Set("content-type", "application/json")

actual1 := h.Cancel(rw, req)
must.Equal(t, tc.exp.err, actual1)

if tc.exp.err != nil {
actual1.ServeHTTP(rw, req)
resp := rw.Body.Bytes()

exp, err := json.Marshal(tc.exp.err)
must.NoError(t, err)

should.Equal(t, exp, bytes.TrimSpace(resp))
return
}
})
}
}

func mustDecimalFromString(v string) decimal.Decimal {
result, err := decimal.NewFromString(v)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions services/skus/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
ErrInvalidOrderNoItems Error = "model: invalid order: no items"
ErrNoStripeCheckoutSessID Error = "model: order: no stripe checkout session id"
ErrInvalidOrderMetadataType Error = "model: order: invalid metadata type"
ErrInvalidUUID Error = "model: invalid uuid"

ErrNumPerIntervalNotSet Error = "model: invalid order: numPerInterval must be set"
ErrNumIntervalsNotSet Error = "model: invalid order: numIntervals must be set"
Expand Down
34 changes: 25 additions & 9 deletions services/skus/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,26 @@ func (s *Service) updateOrderStripeSession(ctx context.Context, dbi sqlx.ExtCont
return s.renewOrderStripe(ctx, dbi, ord, sub.ID, expt, paidt)
}

// CancelOrder cancels an order, propagates to stripe if needed.
//
// TODO(pavelb): Refactor this.
func (s *Service) CancelOrder(orderID uuid.UUID) error {
func (s *Service) CancelOrder(ctx context.Context, id uuid.UUID) error {
tx, err := s.Datastore.RawDB().BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()

if err := s.cancelOrderTx(ctx, tx, id); err != nil {
return err
}

return tx.Commit()
}

func (s *Service) cancelOrderTx(ctx context.Context, dbi sqlx.ExecerContext, id uuid.UUID) error {
return s.orderRepo.SetStatus(ctx, dbi, id, model.OrderStatusCanceled)
}

// CancelOrderLegacy cancels an order, propagates to stripe if needed.
func (s *Service) CancelOrderLegacy(orderID uuid.UUID) error {
// TODO: Refactor this later. Now here's a quick fix.
ord, err := s.Datastore.GetOrder(orderID)
if err != nil {
Expand Down Expand Up @@ -1671,7 +1687,7 @@ func (s *Service) processStripeNotificationTx(ctx context.Context, dbi sqlx.ExtC
return err
}

return s.orderRepo.SetStatus(ctx, dbi, oid, model.OrderStatusCanceled)
return s.cancelOrderTx(ctx, dbi, oid)

default:
return nil
Expand Down Expand Up @@ -1718,7 +1734,7 @@ func (s *Service) processAppStoreNotificationTx(ctx context.Context, dbi sqlx.Ex
return s.renewOrderWithExpPaidTimeTx(ctx, dbi, ord.ID, expt, paidt)

case ntf.shouldCancel():
return s.orderRepo.SetStatus(ctx, dbi, ord.ID, model.OrderStatusCanceled)
return s.cancelOrderTx(ctx, dbi, ord.ID)

default:
return nil
Expand Down Expand Up @@ -1769,11 +1785,11 @@ func (s *Service) processPlayStoreNotificationTx(ctx context.Context, dbi sqlx.E

// Sub cancellation.
case ntf.SubscriptionNtf != nil && ntf.SubscriptionNtf.shouldCancel():
return s.orderRepo.SetStatus(ctx, dbi, ord.ID, model.OrderStatusCanceled)
return s.cancelOrderTx(ctx, dbi, ord.ID)

// Voiding.
case ntf.VoidedPurchaseNtf != nil && ntf.VoidedPurchaseNtf.shouldProcess():
return s.orderRepo.SetStatus(ctx, dbi, ord.ID, model.OrderStatusCanceled)
return s.cancelOrderTx(ctx, dbi, ord.ID)

default:
return nil
Expand Down Expand Up @@ -2339,7 +2355,7 @@ func (s *Service) processRadomNotificationTx(ctx context.Context, dbi sqlx.ExtCo
return err
}

return s.orderRepo.SetStatus(ctx, dbi, ord.ID, model.OrderStatusCanceled)
return s.cancelOrderTx(ctx, dbi, ord.ID)

default:
return errRadomUnknownAction
Expand Down
Loading
Loading