2016-07-12 11:19:30 -06:00
|
|
|
package lila.plan
|
2016-06-06 03:36:21 -06:00
|
|
|
|
2016-06-06 08:41:04 -06:00
|
|
|
import org.joda.time.DateTime
|
2019-12-02 17:42:57 -07:00
|
|
|
import reactivemongo.api._
|
2016-07-18 15:24:32 -06:00
|
|
|
import scala.concurrent.duration._
|
2016-06-06 03:36:21 -06:00
|
|
|
|
2019-12-01 10:24:02 -07:00
|
|
|
import lila.common.config.Secret
|
2019-12-24 16:26:23 -07:00
|
|
|
import lila.common.Bus
|
2019-08-18 07:52:49 -06:00
|
|
|
import lila.db.dsl._
|
2019-12-23 18:01:45 -07:00
|
|
|
import lila.memo.CacheApi._
|
2019-08-18 07:52:49 -06:00
|
|
|
import lila.user.{ User, UserRepo }
|
|
|
|
|
2016-07-12 11:19:30 -06:00
|
|
|
final class PlanApi(
|
|
|
|
stripeClient: StripeClient,
|
2016-07-11 10:01:44 -06:00
|
|
|
patronColl: Coll,
|
2016-07-12 10:33:22 -06:00
|
|
|
chargeColl: Coll,
|
2016-07-14 04:58:34 -06:00
|
|
|
notifier: PlanNotifier,
|
2019-12-01 10:24:02 -07:00
|
|
|
userRepo: UserRepo,
|
2016-07-19 10:15:35 -06:00
|
|
|
lightUserApi: lila.user.LightUserApi,
|
2019-12-23 18:01:45 -07:00
|
|
|
cacheApi: lila.memo.CacheApi,
|
2019-12-24 16:26:23 -07:00
|
|
|
mongoCache: lila.memo.MongoCache.Api,
|
2019-12-01 10:24:02 -07:00
|
|
|
payPalIpnKey: Secret,
|
2021-06-03 06:52:30 -06:00
|
|
|
monthlyGoalApi: MonthlyGoalApi,
|
|
|
|
currencyApi: CurrencyApi,
|
|
|
|
pricingApi: PlanPriceApi
|
2019-12-24 16:26:23 -07:00
|
|
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
2016-06-06 08:41:04 -06:00
|
|
|
|
2016-07-12 10:58:39 -06:00
|
|
|
import BsonHandlers._
|
|
|
|
import PatronHandlers._
|
|
|
|
import ChargeHandlers._
|
2016-06-06 08:41:04 -06:00
|
|
|
|
2021-06-03 06:52:30 -06:00
|
|
|
def switch(user: User, money: Money): Fu[StripeSubscription] =
|
2016-06-06 08:41:04 -06:00
|
|
|
userCustomer(user) flatMap {
|
2016-06-06 12:18:40 -06:00
|
|
|
case None => fufail(s"Can't switch non-existent customer ${user.id}")
|
2016-06-06 08:41:04 -06:00
|
|
|
case Some(customer) =>
|
|
|
|
customer.firstSubscription match {
|
2021-05-30 04:51:14 -06:00
|
|
|
case None => fufail(s"Can't switch non-existent subscription of ${user.id}")
|
2021-06-03 06:52:30 -06:00
|
|
|
case Some(sub) if sub.item.price.money == money => fuccess(sub)
|
|
|
|
case Some(sub) => stripeClient.updateSubscription(sub, money)
|
2016-06-06 08:41:04 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-06 12:18:40 -06:00
|
|
|
def cancel(user: User): Funit =
|
|
|
|
userCustomer(user) flatMap {
|
|
|
|
case None => fufail(s"Can't cancel non-existent customer ${user.id}")
|
|
|
|
case Some(customer) =>
|
|
|
|
customer.firstSubscription match {
|
|
|
|
case None => fufail(s"Can't cancel non-existent subscription of ${user.id}")
|
2019-12-13 07:30:20 -07:00
|
|
|
case Some(sub) =>
|
2020-10-13 02:13:06 -06:00
|
|
|
stripeClient.cancelSubscription(sub) >>
|
2019-12-13 07:30:20 -07:00
|
|
|
isLifetime(user).flatMap { lifetime =>
|
|
|
|
!lifetime ?? setDbUserPlan(user, user.plan.disable)
|
|
|
|
} >>
|
|
|
|
patronColl.update.one($id(user.id), $unset("stripe", "payPal", "expiresAt")).void >>-
|
|
|
|
logger.info(s"Canceled subscription $sub of ${user.username}")
|
2016-06-06 12:18:40 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-03 06:52:30 -06:00
|
|
|
def onStripeCharge(stripeCharge: StripeCharge): Funit = for {
|
|
|
|
patronOption <- customerIdPatron(stripeCharge.customer)
|
|
|
|
money = stripeCharge.amount toMoney stripeCharge.currency
|
|
|
|
usd <- currencyApi toUsd money
|
|
|
|
charge = Charge.make(
|
|
|
|
userId = patronOption.map(_.userId),
|
|
|
|
stripe = Charge.Stripe(stripeCharge.id, stripeCharge.customer).some,
|
|
|
|
money = money,
|
|
|
|
usd = usd | Usd(0)
|
|
|
|
)
|
|
|
|
isLifetime <- pricingApi isLifetime money
|
|
|
|
_ <- addCharge(charge, stripeCharge.country)
|
|
|
|
_ <-
|
|
|
|
patronOption match {
|
|
|
|
case None =>
|
|
|
|
logger.info(s"Charged anon customer $charge")
|
|
|
|
funit
|
|
|
|
case Some(patron) =>
|
|
|
|
logger.info(s"Charged $charge $patron")
|
|
|
|
userRepo byId patron.userId orFail s"Missing user for $patron" flatMap { user =>
|
|
|
|
val p2 = patron
|
|
|
|
.copy(
|
|
|
|
stripe = Patron.Stripe(stripeCharge.customer).some,
|
|
|
|
free = none
|
|
|
|
)
|
|
|
|
.levelUpIfPossible
|
|
|
|
patronColl.update.one($id(patron.id), p2) >>
|
|
|
|
setDbUserPlanOnCharge(user, patron.canLevelUp) >> {
|
|
|
|
isLifetime ?? setLifetime(user)
|
|
|
|
}
|
|
|
|
}
|
2016-07-12 10:33:22 -06:00
|
|
|
}
|
2021-06-03 06:52:30 -06:00
|
|
|
} yield ()
|
2016-06-06 08:41:04 -06:00
|
|
|
|
2016-07-14 13:31:41 -06:00
|
|
|
def onPaypalCharge(
|
2019-12-13 07:30:20 -07:00
|
|
|
userId: Option[User.ID],
|
|
|
|
email: Option[Patron.PayPal.Email],
|
|
|
|
subId: Option[Patron.PayPal.SubId],
|
2021-06-03 06:52:30 -06:00
|
|
|
money: Money,
|
2019-12-13 07:30:20 -07:00
|
|
|
name: Option[String],
|
|
|
|
txnId: Option[String],
|
2021-03-24 08:31:27 -06:00
|
|
|
country: Option[Country],
|
2019-12-13 07:30:20 -07:00
|
|
|
ip: String,
|
|
|
|
key: String
|
2021-06-03 06:52:30 -06:00
|
|
|
): Funit = for {
|
|
|
|
pricing <- pricingApi pricingFor money.currency orFail s"Invalid paypal currency $money"
|
|
|
|
usd <- currencyApi toUsd money orFail s"Invalid paypal currency $money"
|
|
|
|
isLifetime <- pricingApi isLifetime money
|
|
|
|
_ <-
|
|
|
|
if (key != payPalIpnKey.value) {
|
|
|
|
logger.error(s"Invalid PayPal IPN key $key from $ip $userId $money")
|
|
|
|
funit
|
|
|
|
} else if (!pricing.valid(money)) {
|
|
|
|
logger.info(s"Ignoring invalid paypal amount from $ip $userId $money $txnId")
|
|
|
|
funit
|
|
|
|
} else {
|
|
|
|
val charge = Charge.make(
|
|
|
|
userId = userId,
|
|
|
|
payPal = Charge
|
|
|
|
.PayPal(
|
|
|
|
name = name,
|
|
|
|
email = email.map(_.value),
|
|
|
|
txnId = txnId,
|
|
|
|
subId = subId.map(_.value),
|
|
|
|
ip = ip.some
|
|
|
|
)
|
|
|
|
.some,
|
|
|
|
money = money,
|
|
|
|
usd = usd
|
|
|
|
)
|
|
|
|
addCharge(charge, country) >>
|
|
|
|
(userId ?? userRepo.named) flatMap { userOption =>
|
|
|
|
userOption ?? { user =>
|
|
|
|
val payPal = Patron.PayPal(email, subId, DateTime.now)
|
|
|
|
userPatron(user).flatMap {
|
|
|
|
case None =>
|
|
|
|
patronColl.insert.one(
|
|
|
|
Patron(
|
|
|
|
_id = Patron.UserId(user.id),
|
|
|
|
payPal = payPal.some,
|
|
|
|
lastLevelUp = Some(DateTime.now)
|
|
|
|
).expireInOneMonth
|
|
|
|
) >>
|
|
|
|
setDbUserPlanOnCharge(user, levelUp = false)
|
|
|
|
case Some(patron) =>
|
|
|
|
val p2 = patron
|
|
|
|
.copy(
|
|
|
|
payPal = payPal.some,
|
|
|
|
free = none
|
|
|
|
)
|
|
|
|
.levelUpIfPossible
|
|
|
|
.expireInOneMonth
|
|
|
|
patronColl.update.one($id(patron.id), p2) >>
|
|
|
|
setDbUserPlanOnCharge(user, patron.canLevelUp)
|
|
|
|
} >> {
|
|
|
|
isLifetime ?? setLifetime(user)
|
|
|
|
} >>- logger.info(s"Charged ${user.username} with paypal: $money")
|
|
|
|
}
|
2020-09-21 01:28:28 -06:00
|
|
|
}
|
2021-06-03 06:52:30 -06:00
|
|
|
}
|
|
|
|
} yield ()
|
2016-07-11 10:01:44 -06:00
|
|
|
|
2021-05-23 10:03:01 -06:00
|
|
|
private def setDbUserPlanOnCharge(user: User, levelUp: Boolean): Funit = {
|
2017-07-19 18:13:20 -06:00
|
|
|
val plan =
|
2021-05-23 10:03:01 -06:00
|
|
|
if (levelUp) user.plan.incMonths
|
2017-07-19 18:13:20 -06:00
|
|
|
else user.plan.enable
|
2021-05-31 01:46:44 -06:00
|
|
|
Bus.publish(lila.hub.actorApi.plan.MonthInc(user.id, plan.months), "plan")
|
|
|
|
if (plan.months > 1) notifier.onRenew(user.copy(plan = plan))
|
|
|
|
else notifier.onStart(user)
|
2017-07-19 18:13:20 -06:00
|
|
|
setDbUserPlan(user, plan)
|
|
|
|
}
|
|
|
|
|
2016-06-06 08:41:04 -06:00
|
|
|
def onSubscriptionDeleted(sub: StripeSubscription): Funit =
|
2016-07-11 10:01:44 -06:00
|
|
|
customerIdPatron(sub.customer) flatMap {
|
2021-05-31 01:46:44 -06:00
|
|
|
_ ?? { patron =>
|
|
|
|
if (patron.isLifetime) funit
|
|
|
|
else
|
|
|
|
userRepo byId patron.userId orFail s"Missing user for $patron" flatMap { user =>
|
|
|
|
setDbUserPlan(user, user.plan.disable) >>
|
|
|
|
patronColl.update.one($id(user.id), patron.removeStripe).void >>-
|
|
|
|
notifier.onExpire(user) >>-
|
|
|
|
logger.info(s"Unsubed ${user.username} $sub")
|
|
|
|
}
|
|
|
|
}
|
2016-06-06 08:41:04 -06:00
|
|
|
}
|
|
|
|
|
2020-01-01 20:15:05 -07:00
|
|
|
def onCompletedSession(completedSession: StripeCompletedSession): Funit =
|
|
|
|
customerIdPatron(completedSession.customer) flatMap {
|
|
|
|
case None =>
|
|
|
|
logger.warn(s"Completed Session of unknown patron $completedSession")
|
|
|
|
funit
|
2021-05-31 01:46:44 -06:00
|
|
|
case Some(prevPatron) =>
|
|
|
|
userRepo byId prevPatron.userId orFail s"Missing user for $prevPatron" flatMap { user =>
|
|
|
|
val patron = prevPatron
|
|
|
|
.copy(lastLevelUp = Some(DateTime.now))
|
|
|
|
.removePayPal
|
|
|
|
.expireInOneMonth(!completedSession.freq.renew)
|
|
|
|
patronColl.update.one($id(user.id), patron, upsert = true).void
|
2020-01-01 18:23:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-12 11:19:30 -06:00
|
|
|
def getEvent = stripeClient.getEvent _
|
2016-06-06 08:55:05 -06:00
|
|
|
|
2016-07-14 10:27:07 -06:00
|
|
|
def customerInfo(user: User, customer: StripeCustomer): Fu[Option[CustomerInfo]] =
|
|
|
|
stripeClient.getNextInvoice(customer.id) zip
|
2021-06-02 04:06:06 -06:00
|
|
|
stripeClient.getPastInvoices(customer.id) zip
|
|
|
|
customer.firstSubscription.??(stripeClient.getPaymentMethod) map {
|
|
|
|
case ((Some(nextInvoice), pastInvoices), paymentMethod) =>
|
2020-09-21 01:28:28 -06:00
|
|
|
customer.firstSubscription match {
|
2021-06-02 04:06:06 -06:00
|
|
|
case Some(sub) => MonthlyCustomerInfo(sub, nextInvoice, pastInvoices, paymentMethod).some
|
2020-09-21 01:28:28 -06:00
|
|
|
case None =>
|
|
|
|
logger.warn(s"Can't identify ${user.username} monthly subscription $customer")
|
|
|
|
none
|
|
|
|
}
|
2021-06-02 04:06:06 -06:00
|
|
|
case ((None, _), _) => OneTimeCustomerInfo(customer).some
|
2020-09-21 01:28:28 -06:00
|
|
|
}
|
2016-06-06 11:50:13 -06:00
|
|
|
|
2016-07-14 03:26:57 -06:00
|
|
|
import PlanApi.SyncResult.{ ReloadUser, Synced }
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def sync(user: User): Fu[PlanApi.SyncResult] =
|
|
|
|
userPatron(user) flatMap {
|
2016-07-11 16:55:35 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case None if user.plan.active =>
|
|
|
|
logger.warn(s"${user.username} sync: disable plan of non-patron")
|
|
|
|
setDbUserPlan(user, user.plan.disable) inject ReloadUser
|
2016-07-11 16:55:35 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case None => fuccess(Synced(none, none))
|
2016-07-11 16:55:35 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case Some(patron) =>
|
|
|
|
(patron.stripe, patron.payPal) match {
|
|
|
|
|
|
|
|
case (Some(stripe), _) =>
|
|
|
|
stripeClient.getCustomer(stripe.customerId) flatMap {
|
|
|
|
case None =>
|
|
|
|
logger.warn(s"${user.username} sync: unset DB patron that's not in stripe")
|
|
|
|
patronColl.update.one($id(patron.id), patron.removeStripe) >> sync(user)
|
2020-06-15 10:49:06 -06:00
|
|
|
case Some(customer) if customer.firstSubscription.exists(_.isActive) && !user.plan.active =>
|
2020-05-05 22:11:15 -06:00
|
|
|
logger.warn(s"${user.username} sync: enable plan of customer with a subscription")
|
|
|
|
setDbUserPlan(user, user.plan.enable) inject ReloadUser
|
|
|
|
case customer => fuccess(Synced(patron.some, customer))
|
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case (_, Some(_)) =>
|
|
|
|
if (!user.plan.active) {
|
|
|
|
logger.warn(s"${user.username} sync: enable plan of customer with paypal")
|
2019-12-13 07:30:20 -07:00
|
|
|
setDbUserPlan(user, user.plan.enable) inject ReloadUser
|
2020-05-05 22:11:15 -06:00
|
|
|
} else fuccess(Synced(patron.some, none))
|
2016-07-11 16:55:35 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case (None, None) if patron.isLifetime => fuccess(Synced(patron.some, none))
|
2016-07-11 16:55:35 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case (None, None) if user.plan.active && patron.free.isEmpty =>
|
|
|
|
logger.warn(s"${user.username} sync: disable plan of patron with no paypal or stripe")
|
|
|
|
setDbUserPlan(user, user.plan.disable) inject ReloadUser
|
2018-05-23 10:51:55 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
case _ => fuccess(Synced(patron.some, none))
|
|
|
|
}
|
|
|
|
}
|
2016-06-06 11:50:13 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def isLifetime(user: User): Fu[Boolean] =
|
|
|
|
userPatron(user) map {
|
|
|
|
_.exists(_.isLifetime)
|
|
|
|
}
|
2018-04-11 17:34:14 -06:00
|
|
|
|
2021-05-23 13:14:53 -06:00
|
|
|
def setLifetime(user: User): Funit = {
|
2021-05-23 12:49:10 -06:00
|
|
|
if (user.plan.isEmpty) Bus.publish(lila.hub.actorApi.plan.MonthInc(user.id, 0), "plan")
|
2019-12-13 07:30:20 -07:00
|
|
|
userRepo.setPlan(
|
|
|
|
user,
|
2021-05-23 11:06:44 -06:00
|
|
|
user.plan.enable
|
2019-12-13 07:30:20 -07:00
|
|
|
) >> patronColl.update
|
|
|
|
.one(
|
|
|
|
$id(user.id),
|
|
|
|
$set(
|
|
|
|
"lastLevelUp" -> DateTime.now,
|
|
|
|
"lifetime" -> true,
|
|
|
|
"free" -> Patron.Free(DateTime.now)
|
|
|
|
),
|
|
|
|
upsert = true
|
|
|
|
)
|
|
|
|
.void >>- lightUserApi.invalidate(user.id)
|
2021-05-23 13:14:53 -06:00
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
|
|
|
|
def giveMonth(user: User): Funit =
|
2021-05-23 11:06:44 -06:00
|
|
|
patronColl.update
|
2019-12-13 07:30:20 -07:00
|
|
|
.one(
|
|
|
|
$id(user.id),
|
|
|
|
$set(
|
|
|
|
"lastLevelUp" -> DateTime.now,
|
|
|
|
"lifetime" -> false,
|
|
|
|
"free" -> Patron.Free(DateTime.now),
|
2021-06-02 04:06:06 -06:00
|
|
|
"expiresAt" -> DateTime.now.plusMonths(1)
|
2019-12-13 07:30:20 -07:00
|
|
|
),
|
|
|
|
upsert = true
|
|
|
|
)
|
2021-05-23 11:06:44 -06:00
|
|
|
.void >> setDbUserPlanOnCharge(user, levelUp = false)
|
2018-04-11 17:34:14 -06:00
|
|
|
|
2021-04-22 01:19:18 -06:00
|
|
|
def remove(user: User): Funit =
|
2021-05-29 01:08:46 -06:00
|
|
|
userRepo.unsetPlan(user) >>
|
|
|
|
patronColl.unsetField($id(user.id), "lifetime").void >>-
|
|
|
|
lightUserApi.invalidate(user.id)
|
2021-04-22 01:19:18 -06:00
|
|
|
|
2019-08-18 07:52:49 -06:00
|
|
|
private val recentChargeUserIdsNb = 100
|
2019-12-23 18:01:45 -07:00
|
|
|
private val recentChargeUserIdsCache = cacheApi.unit[List[User.ID]] {
|
|
|
|
_.refreshAfterWrite(30 minutes)
|
|
|
|
.buildAsyncFuture { _ =>
|
|
|
|
chargeColl.primitive[User.ID](
|
|
|
|
$empty,
|
|
|
|
sort = $doc("date" -> -1),
|
|
|
|
nb = recentChargeUserIdsNb * 3 / 2,
|
|
|
|
"userId"
|
|
|
|
) flatMap filterUserIds dmap (_ take recentChargeUserIdsNb)
|
|
|
|
}
|
|
|
|
}
|
2016-07-18 15:24:32 -06:00
|
|
|
|
2019-12-23 18:01:45 -07:00
|
|
|
def recentChargeUserIds: Fu[List[User.ID]] = recentChargeUserIdsCache.getUnit
|
2016-07-14 08:41:51 -06:00
|
|
|
|
2019-12-04 23:52:53 -07:00
|
|
|
def recentChargesOf(user: User): Fu[List[Charge]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
chargeColl.find($doc("userId" -> user.id)).sort($doc("date" -> -1)).cursor[Charge]().list()
|
2016-07-14 09:44:18 -06:00
|
|
|
|
2019-08-18 07:52:49 -06:00
|
|
|
private val topPatronUserIdsNb = 300
|
2019-12-24 16:26:23 -07:00
|
|
|
private val topPatronUserIdsCache = mongoCache.unit[List[User.ID]](
|
|
|
|
"patron:top",
|
|
|
|
59 minutes
|
|
|
|
) { loader =>
|
|
|
|
_.refreshAfterWrite(60 minutes)
|
|
|
|
.buildAsyncFuture {
|
|
|
|
loader { _ =>
|
|
|
|
chargeColl
|
|
|
|
.aggregateList(
|
|
|
|
maxDocs = topPatronUserIdsNb * 2,
|
|
|
|
readPreference = ReadPreference.secondaryPreferred
|
|
|
|
) { framework =>
|
|
|
|
import framework._
|
|
|
|
Match($doc("userId" $exists true)) -> List(
|
|
|
|
GroupField("userId")("total" -> SumField("cents")),
|
|
|
|
Sort(Descending("total")),
|
|
|
|
Limit(topPatronUserIdsNb * 3 / 2)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
.dmap {
|
|
|
|
_.flatMap { _.getAsOpt[User.ID]("_id") }
|
|
|
|
} flatMap filterUserIds dmap (_ take topPatronUserIdsNb)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2019-12-24 16:26:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def topPatronUserIds: Fu[List[User.ID]] = topPatronUserIdsCache.get {}
|
2016-07-14 13:50:18 -06:00
|
|
|
|
2016-07-19 16:50:37 -06:00
|
|
|
private def filterUserIds(ids: List[User.ID]): Fu[List[User.ID]] = {
|
|
|
|
val dedup = ids.distinct
|
2019-12-01 10:24:02 -07:00
|
|
|
userRepo.filterByEnabledPatrons(dedup) map { enableds =>
|
2016-10-24 04:33:35 -06:00
|
|
|
dedup filter enableds.contains
|
2016-07-19 16:50:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 08:31:27 -06:00
|
|
|
private def addCharge(charge: Charge, country: Option[Country]): Funit =
|
|
|
|
monitorCharge(charge, country) >>
|
2021-03-24 07:32:17 -06:00
|
|
|
chargeColl.insert.one(charge).void >>- {
|
|
|
|
recentChargeUserIdsCache.invalidateUnit()
|
|
|
|
monthlyGoalApi.get foreach { m =>
|
|
|
|
Bus.publish(
|
|
|
|
lila.hub.actorApi.plan.ChargeEvent(
|
|
|
|
username = charge.userId.flatMap(lightUserApi.sync).fold("Anonymous")(_.name),
|
2021-06-03 06:52:30 -06:00
|
|
|
cents = charge.usd.cents,
|
2021-03-24 07:32:17 -06:00
|
|
|
percent = m.percent,
|
|
|
|
DateTime.now
|
|
|
|
),
|
|
|
|
"plan"
|
|
|
|
)
|
2021-06-03 06:52:30 -06:00
|
|
|
lila.mon.plan.goal.update(m.goal.cents)
|
|
|
|
lila.mon.plan.current.update(m.current.cents)
|
2021-03-24 07:32:17 -06:00
|
|
|
lila.mon.plan.percent.update(m.percent)
|
2021-06-03 06:52:30 -06:00
|
|
|
if (charge.isPayPal) lila.mon.plan.paypal.record(charge.usd.cents)
|
|
|
|
else if (charge.isStripe) lila.mon.plan.stripe.record(charge.usd.cents)
|
2021-03-24 07:32:17 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 08:31:27 -06:00
|
|
|
private def monitorCharge(charge: Charge, country: Option[Country]): Funit = {
|
|
|
|
lila.mon.plan.charge
|
|
|
|
.countryCents(country = country.fold("unknown")(_.code), service = charge.serviceName)
|
2021-06-03 06:52:30 -06:00
|
|
|
.record(charge.usd.cents)
|
2021-03-24 07:32:17 -06:00
|
|
|
charge.userId ?? { userId =>
|
|
|
|
chargeColl.exists($doc("userId" -> userId)) map {
|
2021-03-24 08:31:27 -06:00
|
|
|
case false => lila.mon.plan.charge.first(charge.serviceName).increment().unit
|
2021-03-24 07:32:17 -06:00
|
|
|
case _ =>
|
2016-07-19 10:15:35 -06:00
|
|
|
}
|
2017-01-26 16:23:43 -07:00
|
|
|
}
|
2021-03-24 08:31:27 -06:00
|
|
|
}
|
2016-07-19 00:04:29 -06:00
|
|
|
|
2016-07-18 14:00:57 -06:00
|
|
|
private def setDbUserPlan(user: User, plan: lila.user.Plan): Funit =
|
2019-12-01 10:24:02 -07:00
|
|
|
userRepo.setPlan(user, plan) >>- lightUserApi.invalidate(user.id)
|
2016-07-18 14:00:57 -06:00
|
|
|
|
2020-01-01 18:23:43 -07:00
|
|
|
private def saveStripeCustomer(user: User, customerId: CustomerId): Funit =
|
2020-01-02 21:30:08 -07:00
|
|
|
userPatron(user) flatMap { patronOpt =>
|
2020-01-02 21:34:51 -07:00
|
|
|
val patron = patronOpt
|
|
|
|
.getOrElse(Patron(_id = Patron.UserId(user.id)))
|
|
|
|
.copy(stripe = Patron.Stripe(customerId).some)
|
|
|
|
patronColl.update.one($id(user.id), patron, upsert = true).void
|
2020-01-02 21:30:08 -07:00
|
|
|
}
|
2020-01-01 18:23:43 -07:00
|
|
|
|
2020-01-03 09:15:43 -07:00
|
|
|
def userCustomerId(user: User): Fu[Option[CustomerId]] =
|
2016-07-12 12:25:58 -06:00
|
|
|
userPatron(user) map {
|
|
|
|
_.flatMap { _.stripe.map(_.customerId) }
|
|
|
|
}
|
2016-07-11 10:01:44 -06:00
|
|
|
|
2020-01-03 09:15:43 -07:00
|
|
|
def userCustomer(user: User): Fu[Option[StripeCustomer]] =
|
2016-07-11 10:01:44 -06:00
|
|
|
userCustomerId(user) flatMap {
|
2016-07-12 11:19:30 -06:00
|
|
|
_ ?? stripeClient.getCustomer
|
2016-06-06 08:41:04 -06:00
|
|
|
}
|
2016-07-11 10:01:44 -06:00
|
|
|
|
2020-10-13 02:13:06 -06:00
|
|
|
def getOrMakeCustomer(user: User, data: Checkout): Fu[StripeCustomer] =
|
|
|
|
userCustomer(user) getOrElse makeCustomer(user, data)
|
|
|
|
|
2020-01-03 09:15:43 -07:00
|
|
|
def makeCustomer(user: User, data: Checkout): Fu[StripeCustomer] =
|
2020-01-03 20:06:23 -07:00
|
|
|
stripeClient.createCustomer(user, data) flatMap { customer =>
|
|
|
|
saveStripeCustomer(user, customer.id) inject customer
|
2020-01-01 18:23:43 -07:00
|
|
|
}
|
|
|
|
|
2020-10-13 02:13:06 -06:00
|
|
|
def getOrMakeCustomerId(user: User, data: Checkout): Fu[CustomerId] =
|
|
|
|
getOrMakeCustomer(user, data).map(_.id)
|
|
|
|
|
2016-08-29 17:18:51 -06:00
|
|
|
def patronCustomer(patron: Patron): Fu[Option[StripeCustomer]] =
|
|
|
|
patron.stripe.map(_.customerId) ?? stripeClient.getCustomer
|
2016-07-11 10:01:44 -06:00
|
|
|
|
|
|
|
private def customerIdPatron(id: CustomerId): Fu[Option[Patron]] =
|
2019-12-07 21:49:02 -07:00
|
|
|
patronColl.one[Patron]($doc("stripe.customerId" -> id))
|
2016-07-11 10:01:44 -06:00
|
|
|
|
2019-12-07 21:49:02 -07:00
|
|
|
def userPatron(user: User): Fu[Option[Patron]] = patronColl.one[Patron]($id(user.id))
|
2019-12-29 17:17:22 -07:00
|
|
|
|
2020-01-01 18:23:43 -07:00
|
|
|
def createSession(data: CreateStripeSession): Fu[StripeSession] =
|
2020-01-01 11:53:35 -07:00
|
|
|
data.checkout.freq match {
|
2021-05-30 05:11:43 -06:00
|
|
|
case Freq.Onetime => stripeClient.createOneTimeSession(data)
|
|
|
|
case Freq.Monthly => stripeClient.createMonthlySession(data)
|
2019-12-29 17:17:22 -07:00
|
|
|
}
|
2021-06-02 04:06:06 -06:00
|
|
|
|
|
|
|
def createPaymentUpdateSession(sub: StripeSubscription, nextUrls: NextUrls): Fu[StripeSession] =
|
|
|
|
stripeClient.createPaymentUpdateSession(sub, nextUrls)
|
|
|
|
|
|
|
|
def updatePayment(sub: StripeSubscription, sessionId: String) =
|
|
|
|
stripeClient.getSession(sessionId) flatMap {
|
|
|
|
_ ?? { session =>
|
|
|
|
stripeClient.setCustomerPaymentMethod(sub.customer, session.setup_intent.payment_method) zip
|
|
|
|
stripeClient.setSubscriptionPaymentMethod(sub, session.setup_intent.payment_method) void
|
|
|
|
}
|
|
|
|
}
|
2016-06-06 03:36:21 -06:00
|
|
|
}
|
2016-07-14 03:26:57 -06:00
|
|
|
|
|
|
|
object PlanApi {
|
|
|
|
|
|
|
|
sealed trait SyncResult
|
|
|
|
object SyncResult {
|
2019-12-13 07:30:20 -07:00
|
|
|
case object ReloadUser extends SyncResult
|
2016-07-14 10:27:07 -06:00
|
|
|
case class Synced(patron: Option[Patron], customer: Option[StripeCustomer]) extends SyncResult
|
2016-07-14 03:26:57 -06:00
|
|
|
}
|
|
|
|
}
|