membership-attribute-service/app/components/TouchpointComponents.scala (173 lines of code) (raw):
package components
import com.gu.aws.ProfileName
import com.gu.config
import com.gu.identity.IdapiService
import com.gu.identity.auth._
import com.gu.identity.play.IdentityPlayAuthService
import com.gu.memsub.subsv2.Catalog
import com.gu.memsub.subsv2.services.{CatalogService, FetchCatalog, SubscriptionService}
import com.gu.monitoring.SafeLogger.LogPrefix
import com.gu.monitoring.{SafeLogging, ZuoraMetrics}
import com.gu.okhttp.RequestRunners
import com.gu.touchpoint.TouchpointBackendConfig
import com.gu.zuora.rest.SimpleClient
import com.gu.zuora.soap.Client
import com.gu.zuora.{ZuoraSoapService, rest}
import com.typesafe.config.Config
import configuration.Stage
import monitoring.CreateMetrics
import org.apache.pekko.actor.ActorSystem
import org.http4s.Uri
import scalaz.{-\/, \/-}
import scalaz.std.scalaFuture._
import services._
import services.salesforce.{ContactRepository, CreateScalaforce, SimpleContactRepository}
import services.stripe.{BasicStripeService, ChooseStripe, HttpBasicStripeService}
import services.subscription.CancelSubscription
import services.zuora.payment.{PaymentService, SetPaymentCard}
import services.zuora.rest.{SimpleClientZuoraRestService, ZuoraRestService}
import software.amazon.awssdk.auth.credentials.{
AwsCredentialsProviderChain,
EnvironmentVariableCredentialsProvider,
InstanceProfileCredentialsProvider,
ProfileCredentialsProvider,
}
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.dynamodb.{DynamoDbAsyncClient, DynamoDbAsyncClientBuilder}
import java.util.concurrent.TimeUnit.SECONDS
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
class TouchpointComponents(
stage: Stage,
createMetrics: CreateMetrics,
conf: Config,
supporterProductDataServiceOverride: Option[SupporterProductDataService] = None,
contactRepositoryOverride: Option[ContactRepository] = None,
subscriptionServiceOverride: Option[SubscriptionService[Future]] = None,
zuoraRestServiceOverride: Option[ZuoraRestService] = None,
catalogServiceOverride: Option[Future[Catalog]] = None,
zuoraServiceOverride: Option[ZuoraSoapService with HealthCheckableService] = None,
patronsStripeServiceOverride: Option[BasicStripeService] = None,
chooseStripeOverride: Option[ChooseStripe] = None,
)(implicit
system: ActorSystem,
executionContext: ExecutionContext,
) extends SafeLogging {
lazy val touchpointConfig = conf.getConfig("touchpoint.backend")
lazy val environmentConfig = touchpointConfig.getConfig(s"environments." + stage.value)
lazy val supporterProductDataTable = environmentConfig.getString("supporter-product-data.table")
lazy val productIds = config.SubsV2ProductIds.load(environmentConfig.getConfig("zuora.productIds"))
lazy val backendConfig = TouchpointBackendConfig.byEnv(stage.value, touchpointConfig)
lazy val patronsStripeService: BasicStripeService = {
lazy val patronsBasicHttpStripeService = new HttpBasicStripeService(backendConfig.stripePatrons, RequestRunners.futureRunner)
patronsStripeServiceOverride.getOrElse(patronsBasicHttpStripeService)
}
lazy val salesforce = CreateScalaforce(backendConfig.salesforce, system.scheduler, configuration.ApplicationName.applicationName)
lazy val contactRepository: ContactRepository = {
lazy val simpleContactRepository = new SimpleContactRepository(salesforce)
contactRepositoryOverride.getOrElse(simpleContactRepository)
}
lazy val salesforceService: SalesforceService = new SalesforceService(salesforce)
lazy val CredentialsProvider = AwsCredentialsProviderChain.builder
.credentialsProviders(
ProfileCredentialsProvider.builder.profileName(ProfileName).build,
InstanceProfileCredentialsProvider.builder.asyncCredentialUpdateEnabled(false).build,
EnvironmentVariableCredentialsProvider.create(),
)
.build
private lazy val dynamoClientBuilder: DynamoDbAsyncClientBuilder = DynamoDbAsyncClient.builder
.credentialsProvider(CredentialsProvider)
.region(Region.EU_WEST_1)
private lazy val mapper = new SupporterRatePlanToAttributesMapper(stage)
private lazy val dynamoSupporterProductDataService =
new SupporterProductDataService(dynamoClientBuilder.build(), supporterProductDataTable, mapper, createMetrics)
lazy val supporterProductDataService: SupporterProductDataService =
supporterProductDataServiceOverride.getOrElse(dynamoSupporterProductDataService)
private val zuoraMetrics = new ZuoraMetrics(stage.value, configuration.ApplicationName.applicationName)
lazy val zuoraSoapService = {
lazy val zuoraSoapClient =
new Client(
apiConfig = backendConfig.zuoraSoap,
httpClient = RequestRunners.configurableFutureRunner(timeout = Duration(30, SECONDS)),
metrics = zuoraMetrics,
)
lazy val simpleZuoraSoapService = new ZuoraSoapService(zuoraSoapClient) with HealthCheckableService {
override def checkHealth: Boolean = zuoraSoapClient.isReady
}
zuoraServiceOverride.getOrElse(simpleZuoraSoapService)
}
lazy val zuoraRestClient = SimpleClient(backendConfig.zuoraRest, RequestRunners.configurableFutureRunner(30.seconds))
lazy val zuoraRestService: ZuoraRestService = {
lazy val simpleClientZuoraRestService = new SimpleClientZuoraRestService(zuoraRestClient)
zuoraRestServiceOverride.getOrElse(simpleClientZuoraRestService)
}
private lazy val futureCatalogNoPrefix: Future[Catalog] = {
logger.infoNoPrefix(s"Loading catalog in $stage")
val catalogRestClient = rest.SimpleClient[Future](backendConfig.zuoraRest, RequestRunners.configurableFutureRunner(60.seconds))
catalogServiceOverride.getOrElse(
CatalogService
.read(FetchCatalog.fromZuoraApi(catalogRestClient)(implicitly, LogPrefix.noLogPrefix), productIds)
.flatMap {
case -\/(error) => Future.failed(new RuntimeException(error))
case \/-(result) => Future.successful(result)
},
)
}
def futureCatalog(implicit logPrefix: LogPrefix): Future[Catalog] =
futureCatalogNoPrefix
.recover { case error =>
logger.error(scrub"Failed to load the product catalog from Zuora due to: $error")
throw error
}
lazy val subscriptionService: SubscriptionService[Future] = {
lazy val zuoraSubscriptionService = new SubscriptionService(futureCatalog(_), zuoraRestClient, zuoraSoapService)
subscriptionServiceOverride.getOrElse(zuoraSubscriptionService)
}
lazy val paymentService: PaymentService = new PaymentService(zuoraSoapService)
lazy val idapiService = new IdapiService(backendConfig.idapi, RequestRunners.futureRunner)
lazy val tokenVerifierConfig = OktaTokenValidationConfig(
issuerUrl = OktaIssuerUrl(conf.getString("okta.verifier.issuerUrl")),
audience = Some(OktaAudience(conf.getString("okta.verifier.audience"))),
clientId = None,
)
lazy val identityPlayAuthService: IdentityPlayAuthService = {
val apiConfig = backendConfig.idapi
val idApiUrl = Uri.unsafeFromString(apiConfig.url)
val idapiConfig = IdapiAuthConfig(idApiUrl, apiConfig.token, Some("membership"))
IdentityPlayAuthService.unsafeInit(
idapiConfig,
tokenVerifierConfig,
)
}
lazy val identityAuthService = new services.IdentityAuthService(identityPlayAuthService)
lazy val guardianPatronService =
new GuardianPatronService(
supporterProductDataService,
patronsStripeService,
backendConfig.stripePatrons.stripeCredentials.publicKey,
createMetrics,
)
lazy val chooseStripe: ChooseStripe = chooseStripeOverride.getOrElse(
ChooseStripe.createFor(
backendConfig.stripeUKMembership,
backendConfig.stripeAUMembership,
backendConfig.stripeTortoiseMedia,
),
)
lazy val paymentDetailsForSubscription: PaymentDetailsForSubscription = new PaymentDetailsForSubscription(paymentService, futureCatalog(_))
lazy val accountDetailsFromZuora: AccountDetailsFromZuora =
new AccountDetailsFromZuora(
createMetrics,
zuoraRestService,
contactRepository,
subscriptionService,
chooseStripe,
paymentDetailsForSubscription,
futureCatalog(_),
)
def setPaymentCard(stripePublicKey: String): SetPaymentCard = {
val stripeService = chooseStripe.serviceForPublicKey(stripePublicKey).toRight(s"No Stripe service for public key: $stripePublicKey")
new SetPaymentCard(zuoraSoapService, stripeService)
}
lazy val cancelSubscription = new CancelSubscription(subscriptionService, zuoraRestService)
}