satellite/payments: Implement coupon codes
Full path: satellite/{payments,console},web/satellite * Adds the ability to apply coupon codes from the billing page in the satellite UI. * Flag for coupon code UI is split into two flags - one for the billing page and one for the signup page. This commit implements the first, but not the second. * Update the Stripe dependency to v72, which is necessary to use Stripe's promo code functionality. Change-Id: I19d9815c48205932bef68d87d5cb0b000498fa70
This commit is contained in:
parent
dae6ed7d03
commit
149f6f2626
2
go.mod
2
go.mod
@ -37,7 +37,7 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/stripe/stripe-go v70.15.0+incompatible
|
github.com/stripe/stripe-go/v72 v72.51.0
|
||||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
|
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
|
||||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||||
github.com/zeebo/assert v1.3.0
|
github.com/zeebo/assert v1.3.0
|
||||||
|
5
go.sum
5
go.sum
@ -507,8 +507,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stripe/stripe-go v70.15.0+incompatible h1:hNML7M1zx8RgtepEMlxyu/FpVPrP7KZm1gPFQquJQvM=
|
github.com/stripe/stripe-go/v72 v72.51.0 h1:scXELorHW1SnAfARThO1QayscOsfEIoIAUy0yxoTqxY=
|
||||||
github.com/stripe/stripe-go v70.15.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
github.com/stripe/stripe-go/v72 v72.51.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
@ -660,6 +660,7 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
@ -5,6 +5,7 @@ package consoleapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -308,6 +309,27 @@ func (p *Payments) TokenDeposit(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyCouponCode applies a coupon code to the user's account.
|
||||||
|
func (p *Payments) ApplyCouponCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
var err error
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
// limit the size of the body to prevent excessive memory usage
|
||||||
|
bodyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, 1*1024*1024))
|
||||||
|
if err != nil {
|
||||||
|
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
couponCode := string(bodyBytes)
|
||||||
|
|
||||||
|
err = p.service.Payments().ApplyCouponCode(ctx, couponCode)
|
||||||
|
if err != nil {
|
||||||
|
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// serveJSONError writes JSON error to response output stream.
|
// serveJSONError writes JSON error to response output stream.
|
||||||
func (p *Payments) serveJSONError(w http.ResponseWriter, status int, err error) {
|
func (p *Payments) serveJSONError(w http.ResponseWriter, status int, err error) {
|
||||||
serveJSONError(p.log, w, status, err)
|
serveJSONError(p.log, w, status, err)
|
||||||
|
@ -84,7 +84,8 @@ type Config struct {
|
|||||||
BetaSatelliteFeedbackURL string `help:"url link for for beta satellite feedback" default:""`
|
BetaSatelliteFeedbackURL string `help:"url link for for beta satellite feedback" default:""`
|
||||||
BetaSatelliteSupportURL string `help:"url link for for beta satellite support" default:""`
|
BetaSatelliteSupportURL string `help:"url link for for beta satellite support" default:""`
|
||||||
DocumentationURL string `help:"url link to documentation" default:"https://docs.storj.io/"`
|
DocumentationURL string `help:"url link to documentation" default:"https://docs.storj.io/"`
|
||||||
CouponCodeUIEnabled bool `help:"indicates if user is allowed to add coupon codes to account" default:"false"`
|
CouponCodeBillingUIEnabled bool `help:"indicates if user is allowed to add coupon codes to account from billing" default:"false"`
|
||||||
|
CouponCodeSignupUIEnabled bool `help:"indicates if user is allowed to add coupon codes to account from signup" default:"false"`
|
||||||
FileBrowserFlowDisabled bool `help:"indicates if file browser flow is disabled" default:"false"`
|
FileBrowserFlowDisabled bool `help:"indicates if file browser flow is disabled" default:"false"`
|
||||||
CSPEnabled bool `help:"indicates if Content Security Policy is enabled" devDefault:"false" releaseDefault:"true"`
|
CSPEnabled bool `help:"indicates if Content Security Policy is enabled" devDefault:"false" releaseDefault:"true"`
|
||||||
LinksharingURL string `help:"url link for linksharing requests" default:"https://link.us1.storjshare.io"`
|
LinksharingURL string `help:"url link for linksharing requests" default:"https://link.us1.storjshare.io"`
|
||||||
@ -245,6 +246,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
|
|||||||
paymentsRouter.HandleFunc("/account", paymentController.SetupAccount).Methods(http.MethodPost)
|
paymentsRouter.HandleFunc("/account", paymentController.SetupAccount).Methods(http.MethodPost)
|
||||||
paymentsRouter.HandleFunc("/billing-history", paymentController.BillingHistory).Methods(http.MethodGet)
|
paymentsRouter.HandleFunc("/billing-history", paymentController.BillingHistory).Methods(http.MethodGet)
|
||||||
paymentsRouter.HandleFunc("/tokens/deposit", paymentController.TokenDeposit).Methods(http.MethodPost)
|
paymentsRouter.HandleFunc("/tokens/deposit", paymentController.TokenDeposit).Methods(http.MethodPost)
|
||||||
|
paymentsRouter.HandleFunc("/couponcodes/apply", paymentController.ApplyCouponCode).Methods(http.MethodPatch)
|
||||||
|
|
||||||
bucketsController := consoleapi.NewBuckets(logger, service)
|
bucketsController := consoleapi.NewBuckets(logger, service)
|
||||||
bucketsRouter := router.PathPrefix("/api/v0/buckets").Subrouter()
|
bucketsRouter := router.PathPrefix("/api/v0/buckets").Subrouter()
|
||||||
@ -356,7 +358,8 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
BetaSatelliteFeedbackURL string
|
BetaSatelliteFeedbackURL string
|
||||||
BetaSatelliteSupportURL string
|
BetaSatelliteSupportURL string
|
||||||
DocumentationURL string
|
DocumentationURL string
|
||||||
CouponCodeUIEnabled bool
|
CouponCodeBillingUIEnabled bool
|
||||||
|
CouponCodeSignupUIEnabled bool
|
||||||
FileBrowserFlowDisabled bool
|
FileBrowserFlowDisabled bool
|
||||||
LinksharingURL string
|
LinksharingURL string
|
||||||
PathwayOverviewEnabled bool
|
PathwayOverviewEnabled bool
|
||||||
@ -381,7 +384,8 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
data.BetaSatelliteFeedbackURL = server.config.BetaSatelliteFeedbackURL
|
data.BetaSatelliteFeedbackURL = server.config.BetaSatelliteFeedbackURL
|
||||||
data.BetaSatelliteSupportURL = server.config.BetaSatelliteSupportURL
|
data.BetaSatelliteSupportURL = server.config.BetaSatelliteSupportURL
|
||||||
data.DocumentationURL = server.config.DocumentationURL
|
data.DocumentationURL = server.config.DocumentationURL
|
||||||
data.CouponCodeUIEnabled = server.config.CouponCodeUIEnabled
|
data.CouponCodeBillingUIEnabled = server.config.CouponCodeBillingUIEnabled
|
||||||
|
data.CouponCodeSignupUIEnabled = server.config.CouponCodeSignupUIEnabled
|
||||||
data.FileBrowserFlowDisabled = server.config.FileBrowserFlowDisabled
|
data.FileBrowserFlowDisabled = server.config.FileBrowserFlowDisabled
|
||||||
data.LinksharingURL = server.config.LinksharingURL
|
data.LinksharingURL = server.config.LinksharingURL
|
||||||
data.PathwayOverviewEnabled = server.config.PathwayOverviewEnabled
|
data.PathwayOverviewEnabled = server.config.PathwayOverviewEnabled
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spacemonkeygo/monkit/v3"
|
"github.com/spacemonkeygo/monkit/v3"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@ -529,6 +529,17 @@ func (paymentService PaymentsService) checkProjectInvoicingStatus(ctx context.Co
|
|||||||
return paymentService.service.accounts.CheckProjectInvoicingStatus(ctx, projectID)
|
return paymentService.service.accounts.CheckProjectInvoicingStatus(ctx, projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyCouponCode applies a coupon code to a Stripe customer.
|
||||||
|
func (paymentService PaymentsService) ApplyCouponCode(ctx context.Context, couponCode string) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "apply coupon code")
|
||||||
|
if err != nil {
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
return paymentService.service.accounts.Coupons().ApplyCouponCode(ctx, auth.User.ID, couponCode)
|
||||||
|
}
|
||||||
|
|
||||||
// AddPromotionalCoupon creates new coupon for specified user.
|
// AddPromotionalCoupon creates new coupon for specified user.
|
||||||
func (paymentService PaymentsService) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID) (err error) {
|
func (paymentService PaymentsService) AddPromotionalCoupon(ctx context.Context, userID uuid.UUID) (err error) {
|
||||||
defer mon.Task()(&ctx, userID)(&err)
|
defer mon.Task()(&ctx, userID)(&err)
|
||||||
|
@ -33,6 +33,9 @@ type Coupons interface {
|
|||||||
// a project, payment method and do not have a promotional coupon yet.
|
// a project, payment method and do not have a promotional coupon yet.
|
||||||
// And updates project limits to selected size.
|
// And updates project limits to selected size.
|
||||||
PopulatePromotionalCoupons(ctx context.Context, duration *int, amount int64, projectLimit memory.Size) error
|
PopulatePromotionalCoupons(ctx context.Context, duration *int, amount int64, projectLimit memory.Size) error
|
||||||
|
|
||||||
|
// ApplyCouponCode attempts to apply a coupon code to the user.
|
||||||
|
ApplyCouponCode(ctx context.Context, userID uuid.UUID, couponCode string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coupon is an entity that adds some funds to Accounts balance for some fixed period.
|
// Coupon is an entity that adds some funds to Accounts balance for some fixed period.
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
"storj.io/storj/satellite/payments"
|
"storj.io/storj/satellite/payments"
|
||||||
|
@ -4,13 +4,14 @@
|
|||||||
package stripecoinpayments
|
package stripecoinpayments
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/stripe/stripe-go/charge"
|
"github.com/stripe/stripe-go/v72/charge"
|
||||||
"github.com/stripe/stripe-go/client"
|
"github.com/stripe/stripe-go/v72/client"
|
||||||
"github.com/stripe/stripe-go/customerbalancetransaction"
|
"github.com/stripe/stripe-go/v72/customerbalancetransaction"
|
||||||
"github.com/stripe/stripe-go/invoice"
|
"github.com/stripe/stripe-go/v72/invoice"
|
||||||
"github.com/stripe/stripe-go/invoiceitem"
|
"github.com/stripe/stripe-go/v72/invoiceitem"
|
||||||
"github.com/stripe/stripe-go/paymentmethod"
|
"github.com/stripe/stripe-go/v72/paymentmethod"
|
||||||
|
"github.com/stripe/stripe-go/v72/promotioncode"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ type StripeClient interface {
|
|||||||
InvoiceItems() StripeInvoiceItems
|
InvoiceItems() StripeInvoiceItems
|
||||||
CustomerBalanceTransactions() StripeCustomerBalanceTransactions
|
CustomerBalanceTransactions() StripeCustomerBalanceTransactions
|
||||||
Charges() StripeCharges
|
Charges() StripeCharges
|
||||||
|
PromoCodes() StripePromoCodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripeCustomers Stripe Customers interface.
|
// StripeCustomers Stripe Customers interface.
|
||||||
@ -57,6 +59,11 @@ type StripeCharges interface {
|
|||||||
List(listParams *stripe.ChargeListParams) *charge.Iter
|
List(listParams *stripe.ChargeListParams) *charge.Iter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripePromoCodes is the Stripe PromoCodes interface.
|
||||||
|
type StripePromoCodes interface {
|
||||||
|
List(params *stripe.PromotionCodeListParams) *promotioncode.Iter
|
||||||
|
}
|
||||||
|
|
||||||
// StripeCustomerBalanceTransactions Stripe CustomerBalanceTransactions interface.
|
// StripeCustomerBalanceTransactions Stripe CustomerBalanceTransactions interface.
|
||||||
type StripeCustomerBalanceTransactions interface {
|
type StripeCustomerBalanceTransactions interface {
|
||||||
New(params *stripe.CustomerBalanceTransactionParams) (*stripe.CustomerBalanceTransaction, error)
|
New(params *stripe.CustomerBalanceTransactionParams) (*stripe.CustomerBalanceTransaction, error)
|
||||||
@ -91,6 +98,10 @@ func (s *stripeClient) Charges() StripeCharges {
|
|||||||
return s.client.Charges
|
return s.client.Charges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *stripeClient) PromoCodes() StripePromoCodes {
|
||||||
|
return s.client.PromotionCodes
|
||||||
|
}
|
||||||
|
|
||||||
// NewStripeClient creates Stripe client from configuration.
|
// NewStripeClient creates Stripe client from configuration.
|
||||||
func NewStripeClient(log *zap.Logger, config Config) StripeClient {
|
func NewStripeClient(log *zap.Logger, config Config) StripeClient {
|
||||||
backendConfig := &stripe.BackendConfig{
|
backendConfig := &stripe.BackendConfig{
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
|
|
||||||
"storj.io/common/memory"
|
"storj.io/common/memory"
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
@ -210,3 +210,32 @@ func (coupons *coupons) AddPromotionalCoupon(ctx context.Context, userID uuid.UU
|
|||||||
|
|
||||||
return Error.Wrap(coupons.service.db.Coupons().PopulatePromotionalCoupons(ctx, []uuid.UUID{userID}, couponDuration, coupons.service.CouponValue, coupons.service.CouponProjectLimit))
|
return Error.Wrap(coupons.service.db.Coupons().PopulatePromotionalCoupons(ctx, []uuid.UUID{userID}, couponDuration, coupons.service.CouponValue, coupons.service.CouponProjectLimit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyCouponCode attempts to apply a coupon code to the user via Stripe.
|
||||||
|
func (coupons *coupons) ApplyCouponCode(ctx context.Context, userID uuid.UUID, couponCode string) (err error) {
|
||||||
|
defer mon.Task()(&ctx, userID, couponCode)(&err)
|
||||||
|
|
||||||
|
promoCodeIter := coupons.service.stripeClient.PromoCodes().List(&stripe.PromotionCodeListParams{
|
||||||
|
Code: stripe.String(couponCode),
|
||||||
|
})
|
||||||
|
if !promoCodeIter.Next() {
|
||||||
|
return Error.New("Invalid coupon code")
|
||||||
|
}
|
||||||
|
promoCode := promoCodeIter.PromotionCode()
|
||||||
|
|
||||||
|
customerID, err := coupons.service.db.Customers().GetCustomerID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &stripe.CustomerParams{
|
||||||
|
PromotionCode: stripe.String(promoCode.ID),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = coupons.service.stripeClient.Customers().Update(customerID, params)
|
||||||
|
if err != nil {
|
||||||
|
return Error.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ package stripecoinpayments
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
"storj.io/storj/satellite/payments"
|
"storj.io/storj/satellite/payments"
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"github.com/spacemonkeygo/monkit/v3"
|
"github.com/spacemonkeygo/monkit/v3"
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
@ -10,13 +10,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/stripe/stripe-go/charge"
|
"github.com/stripe/stripe-go/v72/charge"
|
||||||
"github.com/stripe/stripe-go/customerbalancetransaction"
|
"github.com/stripe/stripe-go/v72/customerbalancetransaction"
|
||||||
"github.com/stripe/stripe-go/form"
|
"github.com/stripe/stripe-go/v72/form"
|
||||||
"github.com/stripe/stripe-go/invoice"
|
"github.com/stripe/stripe-go/v72/invoice"
|
||||||
"github.com/stripe/stripe-go/invoiceitem"
|
"github.com/stripe/stripe-go/v72/invoiceitem"
|
||||||
"github.com/stripe/stripe-go/paymentmethod"
|
"github.com/stripe/stripe-go/v72/paymentmethod"
|
||||||
|
"github.com/stripe/stripe-go/v72/promotioncode"
|
||||||
|
|
||||||
"storj.io/common/storj"
|
"storj.io/common/storj"
|
||||||
"storj.io/common/testrand"
|
"storj.io/common/testrand"
|
||||||
@ -48,6 +49,7 @@ type mockStripeState struct {
|
|||||||
invoiceItems *mockInvoiceItems
|
invoiceItems *mockInvoiceItems
|
||||||
customerBalanceTransactions *mockCustomerBalanceTransactions
|
customerBalanceTransactions *mockCustomerBalanceTransactions
|
||||||
charges *mockCharges
|
charges *mockCharges
|
||||||
|
promoCodes *mockPromoCodes
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockStripeClient struct {
|
type mockStripeClient struct {
|
||||||
@ -56,6 +58,11 @@ type mockStripeClient struct {
|
|||||||
*mockStripeState
|
*mockStripeState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockEmptyQuery is a query with no results.
|
||||||
|
var mockEmptyQuery = stripe.Query(func(*stripe.Params, *form.Values) ([]interface{}, stripe.ListContainer, error) {
|
||||||
|
return nil, newListContainer(&stripe.ListMeta{}), nil
|
||||||
|
})
|
||||||
|
|
||||||
// NewStripeMock creates new Stripe client mock.
|
// NewStripeMock creates new Stripe client mock.
|
||||||
//
|
//
|
||||||
// A new mock is returned for each unique id. If this method is called multiple
|
// A new mock is returned for each unique id. If this method is called multiple
|
||||||
@ -78,6 +85,7 @@ func NewStripeMock(id storj.NodeID, customersDB CustomersDB, usersDB console.Use
|
|||||||
invoiceItems: &mockInvoiceItems{},
|
invoiceItems: &mockInvoiceItems{},
|
||||||
customerBalanceTransactions: newMockCustomerBalanceTransactions(),
|
customerBalanceTransactions: newMockCustomerBalanceTransactions(),
|
||||||
charges: &mockCharges{},
|
charges: &mockCharges{},
|
||||||
|
promoCodes: &mockPromoCodes{},
|
||||||
}
|
}
|
||||||
mocks.m[id] = state
|
mocks.m[id] = state
|
||||||
}
|
}
|
||||||
@ -120,6 +128,10 @@ func (m *mockStripeClient) Charges() StripeCharges {
|
|||||||
return m.charges
|
return m.charges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockStripeClient) PromoCodes() StripePromoCodes {
|
||||||
|
return m.promoCodes
|
||||||
|
}
|
||||||
|
|
||||||
type mockCustomers struct {
|
type mockCustomers struct {
|
||||||
customersDB CustomersDB
|
customersDB CustomersDB
|
||||||
usersDB console.Users
|
usersDB console.Users
|
||||||
@ -261,12 +273,27 @@ func newMockPaymentMethods() *mockPaymentMethods {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listContainer implements Stripe's ListContainer interface.
|
||||||
|
type listContainer struct {
|
||||||
|
listMeta *stripe.ListMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListContainer(meta *stripe.ListMeta) *listContainer {
|
||||||
|
return &listContainer{listMeta: meta}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *listContainer) GetListMeta() *stripe.ListMeta {
|
||||||
|
return c.listMeta
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockPaymentMethods) List(listParams *stripe.PaymentMethodListParams) *paymentmethod.Iter {
|
func (m *mockPaymentMethods) List(listParams *stripe.PaymentMethodListParams) *paymentmethod.Iter {
|
||||||
listMeta := stripe.ListMeta{
|
listMeta := &stripe.ListMeta{
|
||||||
HasMore: false,
|
HasMore: false,
|
||||||
TotalCount: uint32(len(m.attached)),
|
TotalCount: uint32(len(m.attached)),
|
||||||
}
|
}
|
||||||
return &paymentmethod.Iter{Iter: stripe.GetIter(nil, func(*stripe.Params, *form.Values) ([]interface{}, stripe.ListMeta, error) {
|
lc := newListContainer(listMeta)
|
||||||
|
|
||||||
|
query := stripe.Query(func(*stripe.Params, *form.Values) ([]interface{}, stripe.ListContainer, error) {
|
||||||
mocks.Lock()
|
mocks.Lock()
|
||||||
defer mocks.Unlock()
|
defer mocks.Unlock()
|
||||||
|
|
||||||
@ -280,8 +307,9 @@ func (m *mockPaymentMethods) List(listParams *stripe.PaymentMethodListParams) *p
|
|||||||
ret[i] = v
|
ret[i] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, listMeta, nil
|
return ret, lc, nil
|
||||||
})}
|
})
|
||||||
|
return &paymentmethod.Iter{Iter: stripe.GetIter(nil, query)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockPaymentMethods) New(params *stripe.PaymentMethodParams) (*stripe.PaymentMethod, error) {
|
func (m *mockPaymentMethods) New(params *stripe.PaymentMethodParams) (*stripe.PaymentMethod, error) {
|
||||||
@ -355,7 +383,7 @@ func (m *mockInvoices) New(params *stripe.InvoiceParams) (*stripe.Invoice, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockInvoices) List(listParams *stripe.InvoiceListParams) *invoice.Iter {
|
func (m *mockInvoices) List(listParams *stripe.InvoiceListParams) *invoice.Iter {
|
||||||
return &invoice.Iter{Iter: &stripe.Iter{}}
|
return &invoice.Iter{Iter: stripe.GetIter(listParams, mockEmptyQuery)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockInvoices) FinalizeInvoice(id string, params *stripe.InvoiceFinalizeParams) (*stripe.Invoice, error) {
|
func (m *mockInvoices) FinalizeInvoice(id string, params *stripe.InvoiceFinalizeParams) (*stripe.Invoice, error) {
|
||||||
@ -370,7 +398,7 @@ func (m *mockInvoiceItems) New(params *stripe.InvoiceItemParams) (*stripe.Invoic
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockInvoiceItems) List(listParams *stripe.InvoiceItemListParams) *invoiceitem.Iter {
|
func (m *mockInvoiceItems) List(listParams *stripe.InvoiceItemListParams) *invoiceitem.Iter {
|
||||||
return &invoiceitem.Iter{Iter: &stripe.Iter{}}
|
return &invoiceitem.Iter{Iter: stripe.GetIter(listParams, mockEmptyQuery)}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockCustomerBalanceTransactions struct {
|
type mockCustomerBalanceTransactions struct {
|
||||||
@ -404,7 +432,7 @@ func (m *mockCustomerBalanceTransactions) List(listParams *stripe.CustomerBalanc
|
|||||||
mocks.Lock()
|
mocks.Lock()
|
||||||
defer mocks.Unlock()
|
defer mocks.Unlock()
|
||||||
|
|
||||||
return &customerbalancetransaction.Iter{Iter: stripe.GetIter(listParams, func(p *stripe.Params, b *form.Values) ([]interface{}, stripe.ListMeta, error) {
|
query := stripe.Query(func(p *stripe.Params, b *form.Values) ([]interface{}, stripe.ListContainer, error) {
|
||||||
txs := m.transactions[*listParams.Customer]
|
txs := m.transactions[*listParams.Customer]
|
||||||
ret := make([]interface{}, len(txs))
|
ret := make([]interface{}, len(txs))
|
||||||
|
|
||||||
@ -412,17 +440,49 @@ func (m *mockCustomerBalanceTransactions) List(listParams *stripe.CustomerBalanc
|
|||||||
ret[i] = v
|
ret[i] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
listMeta := stripe.ListMeta{
|
listMeta := &stripe.ListMeta{
|
||||||
TotalCount: uint32(len(txs)),
|
TotalCount: uint32(len(txs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, listMeta, nil
|
lc := newListContainer(listMeta)
|
||||||
})}
|
|
||||||
|
return ret, lc, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return &customerbalancetransaction.Iter{Iter: stripe.GetIter(listParams, query)}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockCharges struct {
|
type mockCharges struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockCharges) List(listParams *stripe.ChargeListParams) *charge.Iter {
|
func (m *mockCharges) List(listParams *stripe.ChargeListParams) *charge.Iter {
|
||||||
return &charge.Iter{Iter: &stripe.Iter{}}
|
return &charge.Iter{Iter: stripe.GetIter(listParams, mockEmptyQuery)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPromoCodes struct {
|
||||||
|
promoCodes map[string][]*stripe.PromotionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPromoCodes) List(params *stripe.PromotionCodeListParams) *promotioncode.Iter {
|
||||||
|
mocks.Lock()
|
||||||
|
defer mocks.Unlock()
|
||||||
|
|
||||||
|
query := stripe.Query(func(p *stripe.Params, b *form.Values) ([]interface{}, stripe.ListContainer, error) {
|
||||||
|
promoCodes := m.promoCodes[*params.Code]
|
||||||
|
ret := make([]interface{}, len(promoCodes))
|
||||||
|
|
||||||
|
for i, v := range promoCodes {
|
||||||
|
ret[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
listMeta := &stripe.ListMeta{
|
||||||
|
TotalCount: uint32(len(promoCodes)),
|
||||||
|
}
|
||||||
|
|
||||||
|
lc := newListContainer(listMeta)
|
||||||
|
|
||||||
|
return ret, lc, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return &promotioncode.Iter{Iter: stripe.GetIter(params, query)}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
|
|
||||||
"storj.io/common/memory"
|
"storj.io/common/memory"
|
||||||
"storj.io/common/testcontext"
|
"storj.io/common/testcontext"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stripe/stripe-go"
|
"github.com/stripe/stripe-go/v72"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
|
|
||||||
"storj.io/common/errs2"
|
"storj.io/common/errs2"
|
||||||
|
7
scripts/testdata/satellite-config.yaml.lock
vendored
7
scripts/testdata/satellite-config.yaml.lock
vendored
@ -88,8 +88,11 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# url link to contacts page
|
# url link to contacts page
|
||||||
# console.contact-info-url: https://forum.storj.io
|
# console.contact-info-url: https://forum.storj.io
|
||||||
|
|
||||||
# indicates if user is allowed to add coupon codes to account
|
# indicates if user is allowed to add coupon codes to account from billing
|
||||||
# console.coupon-code-ui-enabled: false
|
# console.coupon-code-billing-ui-enabled: false
|
||||||
|
|
||||||
|
# indicates if user is allowed to add coupon codes to account from signup
|
||||||
|
# console.coupon-code-signup-ui-enabled: false
|
||||||
|
|
||||||
# indicates if Content Security Policy is enabled
|
# indicates if Content Security Policy is enabled
|
||||||
# console.csp-enabled: true
|
# console.csp-enabled: true
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
<meta name="beta-satellite-feedback-url" content="{{ .BetaSatelliteFeedbackURL }}">
|
<meta name="beta-satellite-feedback-url" content="{{ .BetaSatelliteFeedbackURL }}">
|
||||||
<meta name="beta-satellite-support-url" content="{{ .BetaSatelliteSupportURL }}">
|
<meta name="beta-satellite-support-url" content="{{ .BetaSatelliteSupportURL }}">
|
||||||
<meta name="documentation-url" content="{{ .DocumentationURL }}">
|
<meta name="documentation-url" content="{{ .DocumentationURL }}">
|
||||||
<meta name="coupon-code-ui-enabled" content="{{ .CouponCodeUIEnabled }}">
|
<meta name="coupon-code-billing-ui-enabled" content="{{ .CouponCodeBillingUIEnabled }}">
|
||||||
|
<meta name="coupon-code-signup-ui-enabled" content="{{ .CouponCodeSignupUIEnabled }}">
|
||||||
<meta name="file-browser-flow-disabled" content="{{ .FileBrowserFlowDisabled }}">
|
<meta name="file-browser-flow-disabled" content="{{ .FileBrowserFlowDisabled }}">
|
||||||
<meta name="linksharing-url" content="{{ .LinksharingURL }}">
|
<meta name="linksharing-url" content="{{ .LinksharingURL }}">
|
||||||
<meta name="storage-tb-price" content="{{ .StorageTBPrice }}">
|
<meta name="storage-tb-price" content="{{ .StorageTBPrice }}">
|
||||||
|
@ -32,7 +32,8 @@ export default class App extends Vue {
|
|||||||
const satelliteName = MetaUtils.getMetaContent('satellite-name');
|
const satelliteName = MetaUtils.getMetaContent('satellite-name');
|
||||||
const partneredSatellitesJson = JSON.parse(MetaUtils.getMetaContent('partnered-satellites'));
|
const partneredSatellitesJson = JSON.parse(MetaUtils.getMetaContent('partnered-satellites'));
|
||||||
const isBetaSatellite = MetaUtils.getMetaContent('is-beta-satellite');
|
const isBetaSatellite = MetaUtils.getMetaContent('is-beta-satellite');
|
||||||
const couponCodeUIEnabled = MetaUtils.getMetaContent('coupon-code-ui-enabled');
|
const couponCodeBillingUIEnabled = MetaUtils.getMetaContent('coupon-code-billing-ui-enabled');
|
||||||
|
const couponCodeSignupUIEnabled = MetaUtils.getMetaContent('coupon-code-signup-ui-enabled');
|
||||||
|
|
||||||
if (satelliteName) {
|
if (satelliteName) {
|
||||||
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName);
|
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName);
|
||||||
@ -55,8 +56,11 @@ export default class App extends Vue {
|
|||||||
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_STATUS, isBetaSatellite === 'true');
|
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_STATUS, isBetaSatellite === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (couponCodeUIEnabled) {
|
if (couponCodeBillingUIEnabled) {
|
||||||
this.$store.dispatch(APP_STATE_ACTIONS.SET_COUPON_CODE_UI_STATUS, couponCodeUIEnabled === 'true');
|
this.$store.dispatch(APP_STATE_ACTIONS.SET_COUPON_CODE_BILLING_UI_STATUS, couponCodeBillingUIEnabled === 'true');
|
||||||
|
}
|
||||||
|
if (couponCodeSignupUIEnabled) {
|
||||||
|
this.$store.dispatch(APP_STATE_ACTIONS.SET_COUPON_CODE_SIGNUP_UI_STATUS, couponCodeSignupUIEnabled === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -253,4 +253,24 @@ export class PaymentsHttpApi implements PaymentsApi {
|
|||||||
|
|
||||||
return new TokenDeposit(result.amount, result.address, result.link);
|
return new TokenDeposit(result.amount, result.address, result.link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* applyCouponCode applies a coupon code.
|
||||||
|
*
|
||||||
|
* @param couponCode
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public async applyCouponCode(couponCode: string): Promise<void> {
|
||||||
|
const path = `${this.ROOT_PATH}/couponcodes/apply`;
|
||||||
|
const response = await this.client.patch(path, couponCode);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new ErrorUnauthorized();
|
||||||
|
}
|
||||||
|
throw new Error(`Can not apply coupon code "${couponCode}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
width="174px"
|
width="174px"
|
||||||
:on-press="onCreateClick"
|
:on-press="onCreateClick"
|
||||||
label="Add Coupon Code"
|
label="Add Coupon Code"
|
||||||
v-if="couponCodeUIEnabled"
|
v-if="couponCodeBillingUIEnabled"
|
||||||
/>
|
/>
|
||||||
<div class="credit-history__container">
|
<div class="credit-history__container">
|
||||||
<div class="credit-history__content">
|
<div class="credit-history__content">
|
||||||
@ -93,10 +93,10 @@ export default class CreditsHistory extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if coupon code ui is enabled
|
* Indicates if coupon code ui is enabled on the billing page.
|
||||||
*/
|
*/
|
||||||
public get couponCodeUIEnabled(): boolean {
|
public get couponCodeBillingUIEnabled(): boolean {
|
||||||
return this.$store.state.appStateModule.couponCodeUIEnabled;
|
return this.$store.state.appStateModule.couponCodeBillingUIEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -22,19 +22,11 @@
|
|||||||
class="add-coupon__check"
|
class="add-coupon__check"
|
||||||
v-if="isCodeValid"
|
v-if="isCodeValid"
|
||||||
/>
|
/>
|
||||||
<VButton
|
|
||||||
label="Validate"
|
|
||||||
class="add-coupon__claim-button"
|
|
||||||
width="120px"
|
|
||||||
height="32px"
|
|
||||||
v-if="!isCodeValid"
|
|
||||||
:on-press="onValidationCheckClick"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<ValidationMessage
|
<ValidationMessage
|
||||||
class="add-coupon__valid-message"
|
class="add-coupon__valid-message"
|
||||||
successMessage="Get 50GB free and start using Storj DCS today."
|
successMessage="Successfully applied coupon code."
|
||||||
errorMessage="Invalid code. Please Try again"
|
:errorMessage="errorMessage"
|
||||||
:isValid="isCodeValid"
|
:isValid="isCodeValid"
|
||||||
:showMessage="showValidationMessage"
|
:showMessage="showValidationMessage"
|
||||||
/>
|
/>
|
||||||
@ -44,6 +36,7 @@
|
|||||||
width="85%"
|
width="85%"
|
||||||
height="44px"
|
height="44px"
|
||||||
v-if="!isSignupView"
|
v-if="!isSignupView"
|
||||||
|
:on-press="onApplyClick"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -59,6 +52,11 @@ import CloseIcon from '@/../static/images/common/closeCross.svg';
|
|||||||
import CheckIcon from '@/../static/images/common/validCheck.svg';
|
import CheckIcon from '@/../static/images/common/validCheck.svg';
|
||||||
|
|
||||||
import { RouteConfig } from '@/router';
|
import { RouteConfig } from '@/router';
|
||||||
|
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||||
|
|
||||||
|
const {
|
||||||
|
APPLY_COUPON_CODE,
|
||||||
|
} = PAYMENTS_ACTIONS;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@ -70,11 +68,12 @@ import { RouteConfig } from '@/router';
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class AddCouponCodeInput extends Vue {
|
export default class AddCouponCodeInput extends Vue {
|
||||||
|
@Prop({default: false})
|
||||||
|
private showValidationMessage = false;
|
||||||
|
private errorMessage = '';
|
||||||
|
private isCodeValid = false;
|
||||||
|
|
||||||
@Prop({default: false})
|
private couponCode = '';
|
||||||
protected readonly isCodeValid: boolean;
|
|
||||||
@Prop({default: false})
|
|
||||||
protected readonly showValidationMessage: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signup view requires some unque styling and element text.
|
* Signup view requires some unque styling and element text.
|
||||||
@ -91,13 +90,27 @@ export default class AddCouponCodeInput extends Vue {
|
|||||||
return this.isSignupView ? 'Add Coupon' : '';
|
return this.isSignupView ? 'Add Coupon' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setCouponCode(value: string): void {
|
||||||
|
this.couponCode = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if coupon code is valid
|
* Check if coupon code is valid
|
||||||
*/
|
*/
|
||||||
public onValidationCheckClick(): boolean {
|
public async onApplyClick() {
|
||||||
return true;
|
try {
|
||||||
}
|
await this.$store.dispatch(APPLY_COUPON_CODE, this.couponCode);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
this.errorMessage = error.message;
|
||||||
|
this.isCodeValid = false;
|
||||||
|
this.showValidationMessage = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isCodeValid = true;
|
||||||
|
this.showValidationMessage = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -118,15 +131,6 @@ export default class AddCouponCodeInput extends Vue {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__claim-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
bottom: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 2px 0;
|
|
||||||
z-index: 23;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__valid-message {
|
&__valid-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
@ -138,10 +142,10 @@ export default class AddCouponCodeInput extends Vue {
|
|||||||
right: 0;
|
right: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
background: #93a1af;
|
background: #2683ff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: darken(#93a1af, 10%);
|
background: #0059d0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +40,8 @@ export default class ValidationMessage extends Vue {
|
|||||||
&__wrapper {
|
&__wrapper {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
height: 52px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-left: 25px;
|
padding: 12px 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
|
@ -32,7 +32,8 @@ export const appStateModule = {
|
|||||||
satelliteName: '',
|
satelliteName: '',
|
||||||
partneredSatellites: new Array<PartneredSatellite>(),
|
partneredSatellites: new Array<PartneredSatellite>(),
|
||||||
isBetaSatellite: false,
|
isBetaSatellite: false,
|
||||||
couponCodeUIEnabled: false,
|
couponCodeBillingUIEnabled: false,
|
||||||
|
couponCodeSignupUIEnabled: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
// Mutation changing add projectMembers members popup visibility
|
// Mutation changing add projectMembers members popup visibility
|
||||||
@ -126,8 +127,11 @@ export const appStateModule = {
|
|||||||
[APP_STATE_MUTATIONS.SET_SATELLITE_STATUS](state: any, isBetaSatellite: boolean): void {
|
[APP_STATE_MUTATIONS.SET_SATELLITE_STATUS](state: any, isBetaSatellite: boolean): void {
|
||||||
state.isBetaSatellite = isBetaSatellite;
|
state.isBetaSatellite = isBetaSatellite;
|
||||||
},
|
},
|
||||||
[APP_STATE_MUTATIONS.SET_COUPON_CODE_UI_STATUS](state: any, couponCodeUIEnabled: boolean): void {
|
[APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS](state: any, couponCodeBillingUIEnabled: boolean): void {
|
||||||
state.couponCodeUIEnabled = couponCodeUIEnabled;
|
state.couponCodeBillingUIEnabled = couponCodeBillingUIEnabled;
|
||||||
|
},
|
||||||
|
[APP_STATE_MUTATIONS.SET_COUPON_CODE_SIGNUP_UI_STATUS](state: any, couponCodeSignupUIEnabled: boolean): void {
|
||||||
|
state.couponCodeSignupUIEnabled = couponCodeSignupUIEnabled;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -261,8 +265,11 @@ export const appStateModule = {
|
|||||||
[APP_STATE_ACTIONS.SET_SATELLITE_STATUS]: function ({commit}: any, isBetaSatellite: boolean): void {
|
[APP_STATE_ACTIONS.SET_SATELLITE_STATUS]: function ({commit}: any, isBetaSatellite: boolean): void {
|
||||||
commit(APP_STATE_MUTATIONS.SET_SATELLITE_STATUS, isBetaSatellite);
|
commit(APP_STATE_MUTATIONS.SET_SATELLITE_STATUS, isBetaSatellite);
|
||||||
},
|
},
|
||||||
[APP_STATE_ACTIONS.SET_COUPON_CODE_UI_STATUS]: function ({commit}: any, couponCodeUIEnabled: boolean): void {
|
[APP_STATE_ACTIONS.SET_COUPON_CODE_BILLING_UI_STATUS]: function ({commit}: any, couponCodeBillingUIEnabled: boolean): void {
|
||||||
commit(APP_STATE_MUTATIONS.SET_COUPON_CODE_UI_STATUS, couponCodeUIEnabled);
|
commit(APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS, couponCodeBillingUIEnabled);
|
||||||
|
},
|
||||||
|
[APP_STATE_ACTIONS.SET_COUPON_CODE_SIGNUP_UI_STATUS]: function ({commit}: any, couponCodeSignupUIEnabled: boolean): void {
|
||||||
|
commit(APP_STATE_MUTATIONS.SET_COUPON_CODE_SIGNUP_UI_STATUS, couponCodeSignupUIEnabled);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,7 @@ export const PAYMENTS_ACTIONS = {
|
|||||||
GET_PROJECT_USAGE_AND_CHARGES: 'getProjectUsageAndCharges',
|
GET_PROJECT_USAGE_AND_CHARGES: 'getProjectUsageAndCharges',
|
||||||
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP: 'getProjectUsageAndChargesCurrentRollup',
|
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP: 'getProjectUsageAndChargesCurrentRollup',
|
||||||
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP: 'getProjectUsageAndChargesPreviousRollup',
|
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP: 'getProjectUsageAndChargesPreviousRollup',
|
||||||
|
APPLY_COUPON_CODE: 'applyCouponCode',
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -75,6 +76,7 @@ const {
|
|||||||
MAKE_TOKEN_DEPOSIT,
|
MAKE_TOKEN_DEPOSIT,
|
||||||
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP,
|
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP,
|
||||||
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP,
|
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP,
|
||||||
|
APPLY_COUPON_CODE,
|
||||||
} = PAYMENTS_ACTIONS;
|
} = PAYMENTS_ACTIONS;
|
||||||
|
|
||||||
export class PaymentsState {
|
export class PaymentsState {
|
||||||
@ -252,6 +254,9 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState>
|
|||||||
commit(SET_PROJECT_USAGE_AND_CHARGES, usageAndCharges);
|
commit(SET_PROJECT_USAGE_AND_CHARGES, usageAndCharges);
|
||||||
commit(SET_PRICE_SUMMARY, usageAndCharges);
|
commit(SET_PRICE_SUMMARY, usageAndCharges);
|
||||||
},
|
},
|
||||||
|
[APPLY_COUPON_CODE]: async function({commit}: any, code: string): Promise<void> {
|
||||||
|
await api.applyCouponCode(code);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
canUserCreateFirstProject: (state: PaymentsState): boolean => {
|
canUserCreateFirstProject: (state: PaymentsState): boolean => {
|
||||||
|
@ -34,5 +34,6 @@ export const APP_STATE_MUTATIONS = {
|
|||||||
SET_SATELLITE_NAME: 'SET_SATELLITE_NAME',
|
SET_SATELLITE_NAME: 'SET_SATELLITE_NAME',
|
||||||
SET_PARTNERED_SATELLITES: 'SET_PARTNERED_SATELLITES',
|
SET_PARTNERED_SATELLITES: 'SET_PARTNERED_SATELLITES',
|
||||||
SET_SATELLITE_STATUS: 'SET_SATELLITE_STATUS',
|
SET_SATELLITE_STATUS: 'SET_SATELLITE_STATUS',
|
||||||
SET_COUPON_CODE_UI_STATUS: 'SET_COUPON_CODE_UI_STATUS',
|
SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS',
|
||||||
|
SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS',
|
||||||
};
|
};
|
||||||
|
@ -69,6 +69,14 @@ export interface PaymentsApi {
|
|||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
makeTokenDeposit(amount: number): Promise<TokenDeposit>;
|
makeTokenDeposit(amount: number): Promise<TokenDeposit>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* applyCouponCode applies a coupon code.
|
||||||
|
*
|
||||||
|
* @param couponCode
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
applyCouponCode(couponCode: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountBalance {
|
export class AccountBalance {
|
||||||
|
@ -28,7 +28,8 @@ export const APP_STATE_ACTIONS = {
|
|||||||
SET_SATELLITE_NAME: 'SET_SATELLITE_NAME',
|
SET_SATELLITE_NAME: 'SET_SATELLITE_NAME',
|
||||||
SET_PARTNERED_SATELLITES: 'SET_PARTNERED_SATELLITES',
|
SET_PARTNERED_SATELLITES: 'SET_PARTNERED_SATELLITES',
|
||||||
SET_SATELLITE_STATUS: 'SET_SATELLITE_STATUS',
|
SET_SATELLITE_STATUS: 'SET_SATELLITE_STATUS',
|
||||||
SET_COUPON_CODE_UI_STATUS: 'SET_COUPON_CODE_UI_STATUS',
|
SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS',
|
||||||
|
SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NOTIFICATION_ACTIONS = {
|
export const NOTIFICATION_ACTIONS = {
|
||||||
|
@ -151,7 +151,7 @@
|
|||||||
is-password="true"
|
is-password="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AddCouponCodeInput v-if="couponCodeUIEnabled" />
|
<AddCouponCodeInput v-if="couponCodeSignupUIEnabled" />
|
||||||
<div v-if="isBetaSatellite" class="register-area__input-area__container__warning">
|
<div v-if="isBetaSatellite" class="register-area__input-area__container__warning">
|
||||||
<div class="register-area__input-area__container__warning__header">
|
<div class="register-area__input-area__container__warning__header">
|
||||||
<label class="container">
|
<label class="container">
|
||||||
@ -463,8 +463,8 @@ export default class RegisterArea extends Vue {
|
|||||||
/**
|
/**
|
||||||
* Indicates if coupon code ui is enabled
|
* Indicates if coupon code ui is enabled
|
||||||
*/
|
*/
|
||||||
public get couponCodeUIEnabled(): boolean {
|
public get couponCodeSignupUIEnabled(): boolean {
|
||||||
return this.$store.state.appStateModule.couponCodeUIEnabled;
|
return this.$store.state.appStateModule.couponCodeSigunpUIEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +50,7 @@ export class PaymentsMock implements PaymentsApi {
|
|||||||
return Promise.resolve(new TokenDeposit(amount, 'testAddress', 'testLink'));
|
return Promise.resolve(new TokenDeposit(amount, 'testAddress', 'testLink'));
|
||||||
}
|
}
|
||||||
|
|
||||||
getPaywallStatus(userId: string): Promise<boolean> {
|
applyCouponCode(code: string): Promise<void> {
|
||||||
throw new Error('Method not implemented');
|
throw new Error('Method not implemented');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user