package repo import ( "database/sql" "encoding/json" "github.com/ente-io/stacktrace" "github.com/awa/go-iap/appstore" "github.com/awa/go-iap/playstore" "github.com/ente-io/museum/ente" "google.golang.org/api/androidpublisher/v3" ) // BillingRepository defines the methods for inserting, updating and retrieving // billing related entities from the underlying repository type BillingRepository struct { DB *sql.DB } // AddSubscription adds a subscription against a userID func (repo *BillingRepository) AddSubscription(s ente.Subscription) (int64, error) { var subscriptionID int64 err := repo.DB.QueryRow(`INSERT INTO subscriptions(user_id, storage, original_transaction_id, expiry_time, product_id, payment_provider, attributes) VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING subscription_id`, s.UserID, s.Storage, s.OriginalTransactionID, s.ExpiryTime, s.ProductID, s.PaymentProvider, s.Attributes).Scan(&subscriptionID) return subscriptionID, stacktrace.Propagate(err, "") } // UpdateSubscriptionExpiryTime updates the expiryTime of a subscription func (repo *BillingRepository) UpdateSubscriptionExpiryTime(subscriptionID int64, expiryTime int64) error { _, err := repo.DB.Exec(`UPDATE subscriptions SET expiry_time = $1 WHERE subscription_id = $2`, expiryTime, subscriptionID) return stacktrace.Propagate(err, "") } // UpdateSubscriptionCancellationStatus updates whether the user will cancel his subscription on period end func (repo *BillingRepository) UpdateSubscriptionCancellationStatus(userID int64, status bool) error { _, err := repo.DB.Exec(`UPDATE subscriptions SET attributes = jsonb_set(attributes, '{isCancelled}', $1::jsonb) WHERE user_id = $2`, status, userID) return stacktrace.Propagate(err, "") } // GetUserSubscription returns the last created subscription for a userID func (repo *BillingRepository) GetUserSubscription(userID int64) (ente.Subscription, error) { var s ente.Subscription row := repo.DB.QueryRow(`SELECT subscription_id, user_id, product_id, storage, original_transaction_id, expiry_time, payment_provider, attributes FROM subscriptions WHERE user_id = $1`, userID) err := row.Scan(&s.ID, &s.UserID, &s.ProductID, &s.Storage, &s.OriginalTransactionID, &s.ExpiryTime, &s.PaymentProvider, &s.Attributes) return s, stacktrace.Propagate(err, "") } // GetSubscriptionForTransaction returns the subscription for a transactionID within a paymentProvider func (repo *BillingRepository) GetSubscriptionForTransaction(transactionID string, paymentProvider ente.PaymentProvider) (ente.Subscription, error) { var s ente.Subscription row := repo.DB.QueryRow(`SELECT subscription_id, user_id, product_id, storage, original_transaction_id, expiry_time, payment_provider, attributes FROM subscriptions WHERE original_transaction_id = $1 AND payment_provider = $2`, transactionID, paymentProvider) err := row.Scan(&s.ID, &s.UserID, &s.ProductID, &s.Storage, &s.OriginalTransactionID, &s.ExpiryTime, &s.PaymentProvider, &s.Attributes) return s, stacktrace.Propagate(err, "") } // UpdateTransactionIDOnDeletion just append `userID:` before original transaction id on account deletion. // This is to ensure that any subscription update isn't accidently applied to the deleted account and // if user want to use same subscription in different ente account, they should be able to do that. func (repo *BillingRepository) UpdateTransactionIDOnDeletion(userID int64) error { _, err := repo.DB.Query(`update subscriptions SET original_transaction_id = user_id || ':' || original_transaction_id where original_transaction_id is not NULL and user_id= $1`, userID) return stacktrace.Propagate(err, "") } // ReplaceSubscription replaces a subscription with a new one func (repo *BillingRepository) ReplaceSubscription(subscriptionID int64, s ente.Subscription) error { _, err := repo.DB.Exec(`UPDATE subscriptions SET storage = $2, original_transaction_id = $3, expiry_time = $4, product_id = $5, payment_provider = $6, attributes = $7 WHERE subscription_id = $1`, subscriptionID, s.Storage, s.OriginalTransactionID, s.ExpiryTime, s.ProductID, s.PaymentProvider, s.Attributes) return stacktrace.Propagate(err, "") } // UpdateSubscription updates a subscription func (repo *BillingRepository) UpdateSubscription( subscriptionID int64, storage int64, paymentProvider ente.PaymentProvider, transactionID string, productID string, expiryTime int64, ) error { _, err := repo.DB.Exec(`UPDATE subscriptions SET storage = $2, original_transaction_id = $3, expiry_time = $4, product_id = $5, payment_provider = $6 WHERE subscription_id = $1`, subscriptionID, storage, transactionID, expiryTime, productID, paymentProvider) return stacktrace.Propagate(err, "") } // LogPlayStorePush logs a notification from PlayStore func (repo *BillingRepository) LogPlayStorePush(userID int64, notification playstore.DeveloperNotification, verificationResponse androidpublisher.SubscriptionPurchase) error { notificationJSON, _ := json.Marshal(notification) responseJSON, _ := json.Marshal(verificationResponse) _, err := repo.DB.Exec(`INSERT INTO subscription_logs(user_id, payment_provider, notification, verification_response) VALUES($1, $2, $3, $4)`, userID, ente.PlayStore, notificationJSON, responseJSON) return stacktrace.Propagate(err, "") } // LogAppStorePush logs a notification from AppStore func (repo *BillingRepository) LogAppStorePush(userID int64, notification appstore.SubscriptionNotification, verificationResponse appstore.IAPResponse) error { notificationJSON, _ := json.Marshal(notification) responseJSON, _ := json.Marshal(verificationResponse) _, err := repo.DB.Exec(`INSERT INTO subscription_logs(user_id, payment_provider, notification, verification_response) VALUES($1, $2, $3, $4)`, userID, ente.AppStore, notificationJSON, responseJSON) return stacktrace.Propagate(err, "") } // LogStripePush logs a notification from Stripe func (repo *BillingRepository) LogStripePush(eventLog ente.StripeEventLog) error { notificationJSON, _ := json.Marshal(eventLog.Event) responseJSON, _ := json.Marshal(eventLog.StripeSubscription) _, err := repo.DB.Exec(`INSERT INTO subscription_logs(user_id, payment_provider, notification, verification_response) VALUES($1, $2, $3, $4)`, eventLog.UserID, ente.Stripe, notificationJSON, responseJSON) return stacktrace.Propagate(err, "") } // LogStripePush logs a subscription modification by an admin func (repo *BillingRepository) LogAdminTriggeredSubscriptionUpdate(r ente.UpdateSubscriptionRequest) error { requestJSON, _ := json.Marshal(r) _, err := repo.DB.Exec(`INSERT INTO subscription_logs(user_id, payment_provider, notification, verification_response) VALUES($1, $2, $3, '{}'::json)`, r.UserID, r.PaymentProvider, requestJSON) return stacktrace.Propagate(err, "") }