Skip to content

Commit

Permalink
Merge branch 'main' into f/email_templates
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmenendez committed Nov 21, 2024
2 parents 223ffa2 + 236e76e commit 16e6bf1
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 52 deletions.
7 changes: 6 additions & 1 deletion api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ func TestMain(m *testing.M) {
// set reset db env var to true
_ = os.Setenv("VOCDONI_MONGO_RESET_DB", "true")
// create a new MongoDB connection with the test database
if testDB, err = db.New(mongoURI, test.RandomDatabaseName(), "subscriptions.json"); err != nil {
// create a new MongoDB connection with the test database
plans, err := db.ReadPlanJSON()
if err != nil {
panic(err)
}
if testDB, err = db.New(mongoURI, test.RandomDatabaseName(), plans); err != nil {
panic(err)
}
defer testDB.Close()
Expand Down
22 changes: 21 additions & 1 deletion api/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,16 @@ This request can be made only by organization admins.
"smsNotification":false
}
},
"censusSizeTiers": [
{
"flatAmount":9900,
"upTo":100
},
{
"flatAmount":79900,
"upTo":1500
}
],
...
]
}
Expand Down Expand Up @@ -882,7 +892,17 @@ This request can be made only by organization admins.
"personalization":false,
"emailReminder":true,
"smsNotification":false
}
},
"censusSizeTiers": [
{
"flatAmount":9900,
"upTo":100
},
{
"flatAmount":79900,
"upTo":1500
}
],
}
```

Expand Down
21 changes: 13 additions & 8 deletions assets/subscriptions.json → assets/plans.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[
{
"ID": 1,
"Name": "Basic",
"StripeID": "prod_R3LTVsjklmuQAL",
"Name": "Essential Annual Plan",
"StripeID": "price_1QBEmzDW6VLep8WGpkwjynXV",
"StartingPrice": 9900,
"Default": false,
"Organization": {
"Memberships": 5,
"SubOrgs": 1
"SubOrgs": 1,
"CensusSize": 0
},
"VotingTypes": {
"Approval": true,
Expand All @@ -21,12 +23,14 @@
},
{
"ID": 2,
"Name": "Pro",
"StripeID": "prod_R0kTryoMNl8I19",
"Name": "Premium Annual Plan",
"StripeID": "price_1Q8iyUDW6VLep8WGWXdjC78r",
"StartingPrice": 30000,
"Default": false,
"Organization": {
"Memberships": 10,
"SubOrgs": 5
"SubOrgs": 5,
"CensusSize": 0
},
"VotingTypes": {
"Approval": true,
Expand All @@ -41,9 +45,10 @@
},
{
"ID": 3,
"Name": "free",
"Name": "Free Plan",
"StripeID": "price_1QMtoJDW6VLep8WGC2vsJ2CV",
"StartingPrice": 0,
"Default": true,
"StripeID": "stripe_789",
"Organization": {
"Memberships": 10,
"SubOrgs": 5,
Expand Down
23 changes: 14 additions & 9 deletions cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func main() {
flag.StringP("privateKey", "k", "", "private key for the Vocdoni account")
flag.BoolP("fullTransparentMode", "a", false, "allow all transactions and do not modify any of them")
flag.String("emailTemplatesPath", "./assets", "path to the email templates")
flag.String("plansFile", "subscriptions.json", "JSON file that contains the subscriptions info")
flag.String("smtpServer", "", "SMTP server")
flag.Int("smtpPort", 587, "SMTP port")
flag.String("smtpUsername", "", "SMTP username")
Expand Down Expand Up @@ -60,7 +59,6 @@ func main() {
// MongoDB vars
mongoURL := viper.GetString("mongoURL")
mongoDB := viper.GetString("mongoDB")
plansFile := viper.GetString("plansFile")
// email vars
emailTemplatesPath := viper.GetString("emailTemplatesPath")
smtpServer := viper.GetString("smtpServer")
Expand All @@ -74,8 +72,20 @@ func main() {
stripeWebhookSecret := viper.GetString("stripeWebhookSecret")

log.Init("debug", "stdout", os.Stderr)
// create Stripe client and include it in the API configuration
var stripeClient *stripe.StripeClient
if stripeApiSecret != "" || stripeWebhookSecret != "" {
stripeClient = stripe.New(stripeApiSecret, stripeWebhookSecret)
} else {
log.Fatalf("stripeApiSecret and stripeWebhookSecret are required")
}
availablePlans, err := stripeClient.GetPlans()
if err != nil || len(availablePlans) == 0 {
log.Fatalf("could not get the available plans: %v", err)
}

// initialize the MongoDB database
database, err := db.New(mongoURL, mongoDB, plansFile)
database, err := db.New(mongoURL, mongoDB, availablePlans)
if err != nil {
log.Fatalf("could not create the MongoDB database: %v", err)
}
Expand Down Expand Up @@ -107,6 +117,7 @@ func main() {
Account: acc,
WebAppURL: webURL,
FullTransparentMode: fullTransparentMode,
StripeClient: stripeClient,
}
// overwrite the email notifications service with the SMTP service if the
// required parameters are set and include it in the API configuration
Expand Down Expand Up @@ -134,12 +145,6 @@ func main() {
}
log.Infow("email service created", "from", fmt.Sprintf("%s <%s>", emailFromName, emailFromAddress))
}
// create Stripe client and include it in the API configuration
if stripeApiSecret != "" || stripeWebhookSecret != "" {
apiConf.StripeClient = stripe.New(stripeApiSecret, stripeWebhookSecret)
} else {
log.Fatalf("stripeApiSecret and stripeWebhookSecret are required")
}
subscriptions := subscriptions.New(&subscriptions.SubscriptionsConfig{
DB: database,
})
Expand Down
23 changes: 4 additions & 19 deletions db/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ func (ms *MongoStorage) initCollections(database string) error {
return err
}
log.Infow("current collections", "collections", currentCollections)
log.Infow("reading plans from file %s", ms.plansFile)
loadedPlans, err := readPlanJSON(ms.plansFile)
if err != nil {
return err
}
// aux method to get a collection if it exists, or create it if it doesn't
getCollection := func(name string) (*mongo.Collection, error) {
alreadyCreated := false
Expand Down Expand Up @@ -70,11 +65,11 @@ func (ms *MongoStorage) initCollections(database string) error {
}
if name == "plans" {
var plans []interface{}
for _, plan := range loadedPlans {
for _, plan := range ms.stripePlans {
plans = append(plans, plan)
}
count, err := ms.client.Database(database).Collection(name).InsertMany(ctx, plans)
if err != nil || len(count.InsertedIDs) != len(loadedPlans) {
if err != nil || len(count.InsertedIDs) != len(ms.stripePlans) {
return nil, fmt.Errorf("failed to insert plans: %w", err)
}
}
Expand Down Expand Up @@ -224,21 +219,11 @@ func dynamicUpdateDocument(item interface{}, alwaysUpdateTags []string) (bson.M,

// readPlanJSON reads a JSON file with an array of subscritpions
// and return it as a Plan array
func readPlanJSON(plansFile string) ([]*Plan, error) {
log.Warnf("Reading subscriptions from %s", plansFile)
file, err := root.Assets.Open(fmt.Sprintf("assets/%s", plansFile))
func ReadPlanJSON() ([]*Plan, error) {
file, err := root.Assets.Open("assets/plans.json")
if err != nil {
return nil, err
}
// file, err := os.Open(plansFile)
// if err != nil {
// return nil, err
// }
// defer func() {
// if err := file.Close(); err != nil {
// log.Warnw("failed to close subscriptions file", "error", err)
// }
// }()

// Create a JSON decoder
decoder := json.NewDecoder(file)
Expand Down
12 changes: 6 additions & 6 deletions db/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import (

// MongoStorage uses an external MongoDB service for stoting the user data and election details.
type MongoStorage struct {
database string
client *mongo.Client
keysLock sync.RWMutex
plansFile string
database string
client *mongo.Client
keysLock sync.RWMutex
stripePlans []*Plan

users *mongo.Collection
verifications *mongo.Collection
Expand All @@ -34,7 +34,7 @@ type Options struct {
Database string
}

func New(url, database, plansFile string) (*MongoStorage, error) {
func New(url, database string, plans []*Plan) (*MongoStorage, error) {
var err error
ms := &MongoStorage{}
if url == "" {
Expand Down Expand Up @@ -67,7 +67,7 @@ func New(url, database, plansFile string) (*MongoStorage, error) {
// init the database client
ms.client = client
ms.database = database
ms.plansFile = plansFile
ms.stripePlans = plans
// init the collections
if err := ms.initCollections(ms.database); err != nil {
return nil, err
Expand Down
6 changes: 5 additions & 1 deletion db/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ func TestMain(m *testing.M) {
// set reset db env var to true
_ = os.Setenv("VOCDONI_MONGO_RESET_DB", "true")
// create a new MongoDB connection with the test database
db, err = New(mongoURI, test.RandomDatabaseName(), "subscriptions.json")
plans, err := ReadPlanJSON()
if err != nil {
panic(err)
}
db, err = New(mongoURI, test.RandomDatabaseName(), plans)
if err != nil {
panic(err)
}
Expand Down
21 changes: 14 additions & 7 deletions db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,20 @@ type Features struct {
}

type Plan struct {
ID uint64 `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
StripeID string `json:"stripeID" bson:"stripeID"`
Default bool `json:"default" bson:"default"`
Organization PlanLimits `json:"organization" bson:"organization"`
VotingTypes VotingTypes `json:"votingTypes" bson:"votingTypes"`
Features Features `json:"features" bson:"features"`
ID uint64 `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
StripeID string `json:"stripeID" bson:"stripeID"`
StartingPrice int64 `json:"startingPrice" bson:"startingPrice"`
Default bool `json:"default" bson:"default"`
Organization PlanLimits `json:"organization" bson:"organization"`
VotingTypes VotingTypes `json:"votingTypes" bson:"votingTypes"`
Features Features `json:"features" bson:"features"`
CensusSizeTiers []PlanTier `json:"censusSizeTiers" bson:"censusSizeTiers"`
}

type PlanTier struct {
Amount int64 `json:"Amount" bson:"Amount"`
UpTo int64 `json:"upTo" bson:"upTo"`
}

type OrganizationSubscription struct {
Expand Down
78 changes: 78 additions & 0 deletions stripe/stripe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ package stripe

import (
"encoding/json"
"fmt"

"github.com/stripe/stripe-go/v81"
"github.com/stripe/stripe-go/v81/customer"
"github.com/stripe/stripe-go/v81/price"
"github.com/stripe/stripe-go/v81/webhook"
"github.com/vocdoni/saas-backend/db"
"go.vocdoni.io/dvote/log"
)

var PricesLookupKeys = []string{
"essential_annual_plan",
"premium_annual_plan",
"free_plan",
}

// StripeClient is a client for interacting with the Stripe API.
// It holds the necessary configuration such as the webhook secret.
type StripeClient struct {
Expand Down Expand Up @@ -57,3 +66,72 @@ func (s *StripeClient) GetInfoFromEvent(event stripe.Event) (*stripe.Customer, *
}
return customer, &subscription, nil
}

func (s *StripeClient) GetPriceByID(priceID string) *stripe.Price {
params := &stripe.PriceSearchParams{
SearchParams: stripe.SearchParams{
Query: fmt.Sprintf("active:'true' AND lookup_key:'%s'", priceID),
},
}
params.AddExpand("data.tiers")
if results := price.Search(params); results.Next() {
return results.Price()
}
return nil
}

func (s *StripeClient) GetPrices(priceIDs []string) []*stripe.Price {
var prices []*stripe.Price
for _, priceID := range priceIDs {
if price := s.GetPriceByID(priceID); price != nil {
prices = append(prices, price)
}
}
return prices
}

func (s *StripeClient) GetPlans() ([]*db.Plan, error) {
var plans []*db.Plan
for i, priceID := range PricesLookupKeys {
if price := s.GetPriceByID(priceID); price != nil {
var organizationData db.PlanLimits
if err := json.Unmarshal([]byte(price.Metadata["Organization"]), &organizationData); err != nil {
return nil, fmt.Errorf("error parsing plan organization metadata JSON: %s\n", err.Error())
}
var votingTypesData db.VotingTypes
if err := json.Unmarshal([]byte(price.Metadata["VotingTypes"]), &votingTypesData); err != nil {
return nil, fmt.Errorf("error parsing plan voting types metadata JSON: %s\n", err.Error())
}
var featuresData db.Features
if err := json.Unmarshal([]byte(price.Metadata["Features"]), &featuresData); err != nil {
return nil, fmt.Errorf("error parsing plan features metadata JSON: %s\n", err.Error())
}
startingPrice := price.UnitAmount
if len(price.Tiers) > 0 {
startingPrice = price.Tiers[0].FlatAmount
}
var tiers []db.PlanTier
for _, tier := range price.Tiers {
if tier.UpTo == 0 {
continue
}
tiers = append(tiers, db.PlanTier{
Amount: tier.FlatAmount,
UpTo: tier.UpTo,
})
}
plans = append(plans, &db.Plan{
ID: uint64(i),
Name: price.Nickname,
StartingPrice: startingPrice,
StripeID: price.ID,
Default: price.Metadata["Default"] == "true",
Organization: organizationData,
VotingTypes: votingTypesData,
Features: featuresData,
CensusSizeTiers: tiers,
})
}
}
return plans, nil
}

0 comments on commit 16e6bf1

Please sign in to comment.