Skip to content

Commit

Permalink
* Reads plans from stripe instead from local file
Browse files Browse the repository at this point in the history
* Uses local file for testing
  • Loading branch information
emmdim committed Nov 20, 2024
1 parent d3130eb commit 7f4dffc
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 51 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
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 @@ -28,7 +28,6 @@ func main() {
flag.StringP("vocdoniApi", "v", "https://api-dev.vocdoni.net/v2", "vocdoni node remote API URL")
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("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 All @@ -55,7 +54,6 @@ func main() {
}
mongoURL := viper.GetString("mongoURL")
mongoDB := viper.GetString("mongoDB")
plansFile := viper.GetString("plansFile")
// email vars
smtpServer := viper.GetString("smtpServer")
smtpPort := viper.GetInt("smtpPort")
Expand All @@ -68,8 +66,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 @@ -100,6 +110,7 @@ func main() {
Client: apiClient,
Account: acc,
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 All @@ -120,12 +131,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
15 changes: 8 additions & 7 deletions db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,14 @@ 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"`
}

type OrganizationSubscription struct {
Expand Down
83 changes: 83 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,77 @@ func (s *StripeClient) GetInfoFromEvent(event stripe.Event) (*stripe.Customer, *
}
return customer, &subscription, nil
}

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

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

func (s *StripeClient) GetPlans() ([]*db.Plan, error) {
var plans []*db.Plan
for i, priceID := range PricesLookupKeys {
price := s.GetPriceByID(priceID)
if price != nil {
var organizationData map[string]int
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 map[string]bool
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 map[string]bool
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
}
plans = append(plans, &db.Plan{
ID: uint64(i),
Name: price.Nickname,
StartingPrice: startingPrice,
StripeID: price.ID,
Default: price.Metadata["Default"] == "true",
Organization: db.PlanLimits{
Memberships: organizationData["Memberships"],
SubOrgs: organizationData["SubOrgs"],
CensusSize: organizationData["CensusSize"],
},
VotingTypes: db.VotingTypes{
Approval: votingTypesData["Approval"],
Ranked: votingTypesData["Ranked"],
Weighted: votingTypesData["Weighted"],
},
Features: db.Features{
Personalization: featuresData["Personalization"],
EmailReminder: featuresData["EmailReminder"],
SmsNotification: featuresData["SmsNotification"],
},
})
}
}
return plans, nil
}

0 comments on commit 7f4dffc

Please sign in to comment.