membership-attribute-service/app/services/zuora/payment/PaymentService.scala (98 lines of code) (raw):

package services.zuora.payment import com.gu.memsub.BillingSchedule.Bill import com.gu.memsub.Subscription._ import com.gu.memsub.promo.LogImplicit._ import com.gu.memsub.subsv2.{Catalog, Subscription} import com.gu.memsub.{Subscription => _, _} import com.gu.monitoring.SafeLogger.LogPrefix import com.gu.monitoring.SafeLogging import com.gu.services.model.PaymentDetails import com.gu.services.model.PaymentDetails.Payment import com.gu.zuora.ZuoraSoapService import com.gu.zuora.soap.models.Queries import com.gu.zuora.soap.models.Queries.Account import com.gu.zuora.soap.models.Queries.PaymentMethod._ import scalaz.std.option._ import scalaz.syntax.monad._ import scalaz.syntax.std.option._ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try class PaymentService(zuoraService: ZuoraSoapService)(implicit ec: ExecutionContext) extends SafeLogging { def paymentDetails( sub: Subscription, defaultMandateIdIfApplicable: Option[String] = None, catalog: Catalog, )(implicit logPrefix: LogPrefix): Future[PaymentDetails] = { val currency = sub.plan(catalog).chargesPrice.currencies.head // I am not convinced this function is very safe, hence the option val eventualMaybeLastPaymentDate = zuoraService .getPaymentSummary(sub.subscriptionNumber, currency) .map(_.current.serviceStartDate.some) .recover { case _ => None } eventualMaybeLastPaymentDate.withLogging(s"lastPaymentDate for $sub") for { account <- zuoraService.getAccount(sub.accountId) eventualBills = getNextBill(sub.id, account, 15).withLogging(s"next bill for $sub") eventualMaybePaymentMethod = getPaymentMethod(account.defaultPaymentMethodId, defaultMandateIdIfApplicable) // kick off async bills <- eventualBills maybePaymentMethod <- eventualMaybePaymentMethod lpd <- eventualMaybeLastPaymentDate } yield { val maybePayment = bills.find(_.amount > 0).map(bill => Payment(Price(bill.amount, currency), bill.date)) val maybeFirstInvoiceDate = bills.headOption.map(_.date) PaymentDetails.fromSubAndPaymentData(sub, maybePaymentMethod, maybePayment, maybeFirstInvoiceDate, lpd, catalog) } } private def buildBankTransferPaymentMethod(defaultMandateIdIfApplicable: Option[String], m: Queries.PaymentMethod): Option[PaymentMethod] = { for { mandateId <- m.mandateId.orElse(defaultMandateIdIfApplicable) accountName <- m.bankTransferAccountName accountNumber <- m.bankTransferAccountNumberMask paymentMethod <- (m.bankTransferType, m.bankCode) match { case (Some("SEPA"), _) => Some(Sepa(mandateId, accountName, accountNumber, m.numConsecutiveFailures, m.paymentMethodStatus)) case (_, Some(sortCode)) => Some(GoCardless(mandateId, accountName, accountNumber, sortCode, m.numConsecutiveFailures, m.paymentMethodStatus)) case _ => None } } yield paymentMethod } private def buildPaymentMethod( defaultMandateIdIfApplicable: Option[String] = None, soapPaymentMethod: Queries.PaymentMethod, ): Option[PaymentMethod] = soapPaymentMethod.`type` match { case `CreditCard` | `CreditCardReferenceTransaction` => val isReferenceTransaction = soapPaymentMethod.`type` == `CreditCardReferenceTransaction` def asInt(num: String) = Try(num.toInt).toOption val m = soapPaymentMethod val details = (m.creditCardNumber |@| m.creditCardExpirationMonth.flatMap(asInt) |@| m.creditCardExpirationYear.flatMap(asInt))(PaymentCardDetails) Some(PaymentCard(isReferenceTransaction, m.creditCardType, details, m.numConsecutiveFailures, m.paymentMethodStatus)) case `BankTransfer` => buildBankTransferPaymentMethod(defaultMandateIdIfApplicable, soapPaymentMethod) case `PayPal` => Some(PayPalMethod(soapPaymentMethod.payPalEmail.get, soapPaymentMethod.numConsecutiveFailures, soapPaymentMethod.paymentMethodStatus)) case _ => None } private def getNextBill(subId: Id, account: Account, numberOfBills: Int)(implicit logPrefix: LogPrefix): Future[List[Bill]] = for { previewInvoiceItems <- zuoraService.previewInvoices(subId, numberOfBills) } yield for { billingSched <- BillingSchedule.fromPreviewInvoiceItems(previewInvoiceItems).toList bill <- billingSched .withCreditBalanceApplied(account.creditBalance) .invoices .list .toList } yield bill def getPaymentMethod(maybePaymentMethodId: Option[String], defaultMandateIdIfApplicable: Option[String] = None)(implicit logPrefix: LogPrefix, ): Future[Option[PaymentMethod]] = (for { paymentMethodId <- maybePaymentMethodId } yield for { soapPaymentMethod <- zuoraService.getPaymentMethod(paymentMethodId).withLogging(s"get payment method for $maybePaymentMethodId") } yield buildPaymentMethod(defaultMandateIdIfApplicable, soapPaymentMethod)) .getOrElse(Future.successful(None)) }