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

Bundles – Handle Location for Multi-Item Orders #1982

Merged
merged 6 commits into from
Oct 12, 2023
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
12 changes: 8 additions & 4 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

<!-- What does this pr do? Use the fixes syntax where possible (fixes #x) -->

### Type of change ( select one )

### Type of Change

- [ ] Product feature
- [ ] Bug fix
Expand All @@ -12,13 +13,15 @@

<!-- Provide link if applicable. -->


### Tested Environments

- [ ] Development
- [ ] Staging
- [ ] Production

### Before submitting this PR:

### Before Requesting Review

- [ ] Does your code build cleanly without any errors or warnings?
- [ ] Have you used auto closing keywords?
Expand All @@ -28,9 +31,10 @@
- [ ] Have you squashed all intermediate commits?
- [ ] Is there a clear title that explains what the PR does?
- [ ] Have you used intuitive function, variable and other naming?
- [ ] Have you requested security / privacy review if needed
- [ ] Have you requested security and/or privacy review if needed
- [ ] Have you performed a self review of this PR?

### Manual Test Plan:

### Manual Test Plan

<!-- if needed - e.g. prod branch release PR, otherwise remove this section -->
41 changes: 11 additions & 30 deletions services/skus/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ const (
errNotFound = model.Error("not found")
)

// Datastore abstracts over the underlying datastore
// Datastore abstracts over the underlying datastore.
type Datastore interface {
datastore.Datastore
// CreateOrder is used to create an order for payments
CreateOrder(totalPrice decimal.Decimal, merchantID string, status string, currency string, location string, validFor *time.Duration, orderItems []OrderItem, allowedPaymentMethods []string) (*Order, error)

CreateOrder(ctx context.Context, dbi sqlx.ExtContext, oreq *model.OrderNew, items []model.OrderItem) (*model.Order, error)
// SetOrderTrialDays - set the number of days of free trial for this order
SetOrderTrialDays(ctx context.Context, orderID *uuid.UUID, days int64) (*Order, error)
// GetOrder by ID
Expand Down Expand Up @@ -101,14 +101,7 @@ type Datastore interface {
type orderStore interface {
Get(ctx context.Context, dbi sqlx.QueryerContext, id uuid.UUID) (*model.Order, error)
GetByExternalID(ctx context.Context, dbi sqlx.QueryerContext, extID string) (*model.Order, error)
Create(
ctx context.Context,
dbi sqlx.QueryerContext,
totalPrice decimal.Decimal,
merchantID, status, currency, location string,
paymentMethods []string,
validFor *time.Duration,
) (*model.Order, error)
Create(ctx context.Context, dbi sqlx.QueryerContext, oreq *model.OrderNew) (*model.Order, error)
SetLastPaidAt(ctx context.Context, dbi sqlx.ExecerContext, id uuid.UUID, when time.Time) error
SetTrialDays(ctx context.Context, dbi sqlx.QueryerContext, id uuid.UUID, ndays int64) (*model.Order, error)
SetStatus(ctx context.Context, dbi sqlx.ExecerContext, id uuid.UUID, status string) error
Expand Down Expand Up @@ -301,38 +294,26 @@ func (pg *Postgres) SetOrderTrialDays(ctx context.Context, orderID *uuid.UUID, d
return result, nil
}

// CreateOrder creates an order with the given total price, merchant ID, status and orderItems.
func (pg *Postgres) CreateOrder(totalPrice decimal.Decimal, merchantID, status, currency, location string, validFor *time.Duration, orderItems []OrderItem, allowedPaymentMethods []string) (*Order, error) {
tx, err := pg.RawDB().Beginx()
// CreateOrder creates an order from the given prototype, and inserts items.
func (pg *Postgres) CreateOrder(ctx context.Context, dbi sqlx.ExtContext, oreq *model.OrderNew, items []model.OrderItem) (*model.Order, error) {
result, err := pg.orderRepo.Create(ctx, dbi, oreq)
if err != nil {
return nil, err
}
defer pg.RollbackTx(tx)

ctx := context.TODO()

result, err := pg.orderRepo.Create(ctx, tx, totalPrice, merchantID, status, currency, location, allowedPaymentMethods, validFor)
if err != nil {
return nil, err
}

if status == OrderStatusPaid {
if err := pg.recordOrderPayment(ctx, tx, result.ID, time.Now()); err != nil {
if oreq.Status == OrderStatusPaid {
if err := pg.recordOrderPayment(ctx, dbi, result.ID, time.Now()); err != nil {
return nil, fmt.Errorf("failed to record order payment: %w", err)
}
}

model.OrderItemList(orderItems).SetOrderID(result.ID)
model.OrderItemList(items).SetOrderID(result.ID)

result.Items, err = pg.orderItemRepo.InsertMany(ctx, tx, orderItems...)
result.Items, err = pg.orderItemRepo.InsertMany(ctx, dbi, items...)
if err != nil {
return nil, err
}

if err := tx.Commit(); err != nil {
return nil, err
}

return result, nil
}

Expand Down
73 changes: 43 additions & 30 deletions services/skus/datastore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package skus

import (
"context"
"database/sql"
"encoding/json"
"os"
"strings"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/golang/mock/gomock"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
uuid "github.com/satori/go.uuid"
"github.com/shopspring/decimal"
must "github.com/stretchr/testify/require"
Expand Down Expand Up @@ -471,16 +473,21 @@ func createOrderAndIssuer(t *testing.T, ctx context.Context, storage Datastore,
}

validFor := 3600 * time.Second * 24
order, err := storage.CreateOrder(
decimal.NewFromInt32(int32(test.RandomInt())),
test.RandomString(),
OrderStatusPaid,
test.RandomString(),
test.RandomString(),
&validFor,
orderItems,
methods,
)

oreq := &model.OrderNew{
MerchantID: test.RandomString(),
Currency: test.RandomString(),
Status: model.OrderStatusPaid,
TotalPrice: decimal.NewFromInt(int64(test.RandomInt())),
Location: sql.NullString{
Valid: true,
String: test.RandomString(),
},
AllowedPaymentMethods: pq.StringArray(methods),
ValidFor: &validFor,
}

order, err := storage.CreateOrder(ctx, storage.RawDB(), oreq, orderItems)
must.NoError(t, err)

{
Expand Down Expand Up @@ -514,16 +521,19 @@ func (suite *PostgresTestSuite) createTimeLimitedV2OrderCreds(t *testing.T, ctx
methods = append(methods, method...)
}

order, err := suite.storage.CreateOrder(
decimal.NewFromInt32(int32(test.RandomInt())),
test.RandomString(),
OrderStatusPaid,
test.RandomString(),
test.RandomString(),
nil,
orderItems,
methods,
)
oreq := &model.OrderNew{
MerchantID: test.RandomString(),
Currency: test.RandomString(),
Status: model.OrderStatusPaid,
TotalPrice: decimal.NewFromInt(int64(test.RandomInt())),
Location: sql.NullString{
Valid: true,
String: test.RandomString(),
},
AllowedPaymentMethods: pq.StringArray(methods),
}

order, err := suite.storage.CreateOrder(ctx, suite.storage.RawDB(), oreq, orderItems)
must.NoError(t, err)

repo := repository.NewIssuer()
Expand Down Expand Up @@ -595,16 +605,19 @@ func (suite *PostgresTestSuite) createOrderCreds(t *testing.T, ctx context.Conte
methods = append(methods, method...)
}

order, err := suite.storage.CreateOrder(
decimal.NewFromInt32(int32(test.RandomInt())),
test.RandomString(),
OrderStatusPaid,
test.RandomString(),
test.RandomString(),
nil,
orderItems,
methods,
)
oreq := &model.OrderNew{
MerchantID: test.RandomString(),
Currency: test.RandomString(),
Status: model.OrderStatusPaid,
TotalPrice: decimal.NewFromInt(int64(test.RandomInt())),
Location: sql.NullString{
Valid: true,
String: test.RandomString(),
},
AllowedPaymentMethods: pq.StringArray(methods),
}

order, err := suite.storage.CreateOrder(ctx, suite.storage.RawDB(), oreq, orderItems)
must.NoError(t, err)

pk := test.RandomString()
Expand Down
15 changes: 3 additions & 12 deletions services/skus/instrumented_datastore.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions services/skus/mockdatastore.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 50 additions & 24 deletions services/skus/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,52 +93,59 @@ func (o *Order) IsRadomPayable() bool {
}

// CreateStripeCheckoutSession creates a Stripe checkout session for the order.
//
// Deprecated: Use CreateStripeCheckoutSession function instead of this method.
func (o *Order) CreateStripeCheckoutSession(
email, successURI, cancelURI string,
freeTrialDays int64,
) (CreateCheckoutSessionResponse, error) {
return CreateStripeCheckoutSession(o.ID.String(), email, successURI, cancelURI, freeTrialDays, o.Items)
}

// CreateStripeCheckoutSession creates a Stripe checkout session for the order.
func CreateStripeCheckoutSession(
oid, email, successURI, cancelURI string,
trialDays int64,
items []OrderItem,
) (CreateCheckoutSessionResponse, error) {
var custID string
if email != "" {
// find the existing customer by email
// so we can use the customer id instead of a customer email
i := customer.List(&stripe.CustomerListParams{
// Find the existing customer by email to use the customer id instead email.
l := customer.List(&stripe.CustomerListParams{
Email: stripe.String(email),
})

for i.Next() {
custID = i.Customer().ID
for l.Next() {
custID = l.Customer().ID
}
}

sd := &stripe.CheckoutSessionSubscriptionDataParams{}
// If a free trial is set, apply it.
if freeTrialDays > 0 {
sd.TrialPeriodDays = &freeTrialDays
params := &stripe.CheckoutSessionParams{
PaymentMethodTypes: stripe.StringSlice([]string{"card"}),
Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
SuccessURL: stripe.String(successURI),
CancelURL: stripe.String(cancelURI),
ClientReferenceID: stripe.String(oid),
SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{},
LineItems: OrderItemList(items).stripeLineItems(),
}

params := &stripe.CheckoutSessionParams{
PaymentMethodTypes: stripe.StringSlice([]string{
"card",
}),
Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
SuccessURL: stripe.String(successURI),
CancelURL: stripe.String(cancelURI),
ClientReferenceID: stripe.String(o.ID.String()),
SubscriptionData: sd,
LineItems: OrderItemList(o.Items).stripeLineItems(),
// If a free trial is set, apply it.
if trialDays > 0 {
params.SubscriptionData.TrialPeriodDays = &trialDays
}

if custID != "" {
// try to use existing customer we found by email
// Use existing customer if found.
params.Customer = stripe.String(custID)
} else if email != "" {
// if we dont have an existing customer, this CustomerEmail param will create a new one
// Otherwise, create a new using email.
params.CustomerEmail = stripe.String(email)
}
// else we have no record of this email for this checkout session
// the user will be asked for the email, we cannot send an empty customer email as a param
// Otherwise, we have no record of this email for this checkout session.
// ? The user will be asked for the email, we cannot send an empty customer email as a param.

params.SubscriptionData.AddMetadata("orderID", o.ID.String())
params.SubscriptionData.AddMetadata("orderID", oid)
params.AddExtra("allow_promotion_codes", "true")

session, err := session.New(params)
Expand Down Expand Up @@ -264,6 +271,25 @@ type OrderItem struct {
IssuerConfig *IssuerConfig `json:"-" db:"-"`
}

func (x *OrderItem) IsLeo() bool {
if x == nil {
return false
}

return x.SKU == "brave-leo-premium"
}

// OrderNew represents a request to create an order in the database.
type OrderNew struct {
MerchantID string `db:"merchant_id"`
Currency string `db:"currency"`
Status string `db:"status"`
Location sql.NullString `db:"location"`
TotalPrice decimal.Decimal `db:"total_price"`
AllowedPaymentMethods pq.StringArray `db:"allowed_payment_methods"`
ValidFor *time.Duration `db:"valid_for"`
}

// CreateCheckoutSessionResponse represents a checkout session response.
type CreateCheckoutSessionResponse struct {
SessionID string `json:"checkoutSessionId"`
Expand Down
Loading
Loading