/** * Generated by API Builder - https://www.apibuilder.io * Service version: 0.0.2 * User agent: apibuilder app.apibuilder.io/hbc/api-fraud/0.0.2/http4s_0_22 */ package com.hbc.api.fraud.v0.models { /** * Envelope data */ final case class AdditionalData( inAuthId: _root_.java.util.UUID ) /** * General address model */ final case class Address( name: String, streetLineOne: String, streetLineTwo: _root_.scala.Option[String] = None, city: String, state: String, zip: String, country: String ) /** * A customer's complete credit card information * * @param expiryDate MM-YY that the credit card expires * @param authCode CVV code on the back of the card * @param avsResponse Address Verification System response, Codes are available here: * https://www.chasepaymentech.com/address_verification_service.html */ final case class CreditCard( nameOnCard: String, billingAddress: com.hbc.api.fraud.v0.models.Address, cardType: String, cardNumber: String, authorizedAmount: BigDecimal, fullAmount: BigDecimal, expiryDate: String, authCode: String, responseCode: Int, merchantId: _root_.scala.Option[Int] = None, registerNumber: _root_.scala.Option[String] = None, transactionNumber: String, avsResponse: String ) /** * Customer model */ final case class Customer( customerType: com.hbc.api.fraud.v0.models.CustomerType, accountId: String, creationDate: _root_.java.time.LocalDate, firstName: String, lastName: String, emailAddress: String ) /** * Response model to OMS containing the transaction they've sent and the decision * we've made * * @param createdDate Date this record was first created within the database */ final case class DBRecord( transactionId: String, lastModifiedDate: _root_.scala.Option[_root_.java.time.LocalDate] = None, createdDate: _root_.scala.Option[_root_.java.time.LocalDate] = None, transaction: com.hbc.api.fraud.v0.models.Transaction, decision: com.hbc.api.fraud.v0.models.Decision ) /** * Defines decision information */ final case class Decision( transactionId: String, recommendationCode: com.hbc.api.fraud.v0.models.RecommendationCode, crossReference: _root_.scala.Option[_root_.java.util.UUID] = None, rulesTripped: _root_.scala.Option[String] = None, totalScore: Int, remarks: _root_.scala.Option[String] = None ) /** * Error response model * * @param message Error description message * @param error Error code */ final case class Error( message: String, error: String ) /** * Single item * * @param svsNumber Product style number (parent number) * @param skuNumber HBC internally generated number for items (alias SKN) * @param upcNumber The number on the barcode that could be used across different retailers * @param classNumber Signifies business category of an item (pants, tops, shirts, etc.) * @param price Price of single item * @param extendedPrice Total price of all items ordered */ final case class Item( svsNumber: Int, skuNumber: Int, upcNumber: _root_.scala.Option[Int] = None, classNumber: _root_.scala.Option[Int] = None, color: _root_.scala.Option[String] = None, size: _root_.scala.Option[String] = None, currentStatus: String, department: Int, signOnDelivery: _root_.scala.Option[String] = None, giftWrapped: _root_.scala.Option[Boolean] = None, costAmount: _root_.scala.Option[BigDecimal] = None, marginAmount: _root_.scala.Option[BigDecimal] = None, description: String, price: BigDecimal, extendedPrice: BigDecimal, quantity: Int, groupNumber: Int, shippingAddress: com.hbc.api.fraud.v0.models.Address, shippingMethod: String, shippingCountry: String ) /** * Promotion model * * @param promotionAmount Dollar amount discounted by the promotion */ final case class Promo( promotionCode: String, promotionAmount: BigDecimal ) /** * Defines data expected to come in from OMS describing a transaction * * @param source Order source (web, store, etc) * @param additionalData Additional `envelope` data sent through our system to f-s, should include * InAuthId */ final case class Transaction( orderId: String, groupId: String, orderTotal: BigDecimal, shippingCost: BigDecimal, currencyCode: String, storeNumber: Int, salesRep: _root_.scala.Option[String] = None, lastModifiedDate: _root_.scala.Option[_root_.java.time.LocalDate] = None, banner: com.hbc.api.fraud.v0.models.Banner, creditCard: com.hbc.api.fraud.v0.models.CreditCard, promos: _root_.scala.Option[Seq[com.hbc.api.fraud.v0.models.Promo]] = None, items: Seq[com.hbc.api.fraud.v0.models.Item], ipAddress: _root_.scala.Option[String] = None, source: String, customer: com.hbc.api.fraud.v0.models.Customer, additionalData: _root_.scala.Option[com.hbc.api.fraud.v0.models.AdditionalData] = None ) /** * Our lovely store chains */ sealed trait Banner extends _root_.scala.Product with _root_.scala.Serializable object Banner { case object Lat extends Banner { override def toString = "lat" } case object S5a extends Banner { override def toString = "s5a" } case object O5a extends Banner { override def toString = "o5a" } case object Bay extends Banner { override def toString = "bay" } /** * UNDEFINED captures values that are sent either in error or * that were added by the server after this library was * generated. We want to make it easy and obvious to users of * this library to handle this case gracefully. * * We use all CAPS for the variable name to avoid collisions * with the camel cased values above. */ final case class UNDEFINED(override val toString: String) extends Banner /** * all returns a list of all the valid, known values. We use * lower case to avoid collisions with the camel cased values * above. */ val all: scala.List[Banner] = scala.List(Lat, S5a, O5a, Bay) private val byName: Map[String, Banner] = all.map(x => x.toString.toLowerCase -> x).toMap def apply(value: String): Banner = fromString(value).getOrElse(UNDEFINED(value)) def fromString(value: String): _root_.scala.Option[Banner] = byName.get(value.toLowerCase) } /** * Possible types of customers */ sealed trait CustomerType extends _root_.scala.Product with _root_.scala.Serializable object CustomerType { case object Guest extends CustomerType { override def toString = "guest" } case object Registered extends CustomerType { override def toString = "registered" } case object Employee extends CustomerType { override def toString = "employee" } /** * UNDEFINED captures values that are sent either in error or * that were added by the server after this library was * generated. We want to make it easy and obvious to users of * this library to handle this case gracefully. * * We use all CAPS for the variable name to avoid collisions * with the camel cased values above. */ final case class UNDEFINED(override val toString: String) extends CustomerType /** * all returns a list of all the valid, known values. We use * lower case to avoid collisions with the camel cased values * above. */ val all: scala.List[CustomerType] = scala.List(Guest, Registered, Employee) private val byName: Map[String, CustomerType] = all.map(x => x.toString.toLowerCase -> x).toMap def apply(value: String): CustomerType = fromString(value).getOrElse(UNDEFINED(value)) def fromString(value: String): _root_.scala.Option[CustomerType] = byName.get(value.toLowerCase) } /** * Enum describing the possible responses */ sealed trait RecommendationCode extends _root_.scala.Product with _root_.scala.Serializable object RecommendationCode { case object Reject extends RecommendationCode { override def toString = "reject" } case object Review extends RecommendationCode { override def toString = "review" } case object Accept extends RecommendationCode { override def toString = "accept" } /** * UNDEFINED captures values that are sent either in error or * that were added by the server after this library was * generated. We want to make it easy and obvious to users of * this library to handle this case gracefully. * * We use all CAPS for the variable name to avoid collisions * with the camel cased values above. */ final case class UNDEFINED(override val toString: String) extends RecommendationCode /** * all returns a list of all the valid, known values. We use * lower case to avoid collisions with the camel cased values * above. */ val all: scala.List[RecommendationCode] = scala.List(Reject, Review, Accept) private val byName: Map[String, RecommendationCode] = all.map(x => x.toString.toLowerCase -> x).toMap def apply(value: String): RecommendationCode = fromString(value).getOrElse(UNDEFINED(value)) def fromString(value: String): _root_.scala.Option[RecommendationCode] = byName.get(value.toLowerCase) } } package com.hbc.api.fraud.v0.models { package object json { import io.circe.Decoder._ import io.circe.Encoder._ import scala.language.implicitConversions // See below - Make Scala 2.11 Either monadic import scala.util.Try import io.circe.{Json, JsonObject, Encoder, Decoder, DecodingFailure} import io.circe.syntax._ import com.hbc.api.fraud.v0.models.json._ // Make Scala 2.11 Either monadic private[v0] implicit def eitherOps[A,B](e: Either[A,B]) = cats.implicits.catsSyntaxEither(e) private[v0] implicit val decodeUUID: Decoder[_root_.java.util.UUID] = Decoder.decodeString.emapTry(str => Try(_root_.java.util.UUID.fromString(str))) private[v0] implicit val encodeUUID: Encoder[_root_.java.util.UUID] = Encoder.encodeString.contramap[_root_.java.util.UUID](uuid => uuid.toString) private[v0] implicit val decodeInstant: Decoder[_root_.java.time.Instant] = Decoder.decodeString.emapTry(str => Try(_root_.java.time.OffsetDateTime.parse(str).toInstant)) private[v0] implicit val encodeInstant: Encoder[_root_.java.time.Instant] = Encoder.encodeString.contramap[_root_.java.time.Instant](_.toString) private[v0] implicit val decodeLocalDate: Decoder[_root_.java.time.LocalDate] = Decoder.decodeString.emapTry(str => Try(_root_.java.time.LocalDate.parse(str))) private[v0] implicit val encodeLocalDate: Encoder[_root_.java.time.LocalDate] = Encoder.encodeString.contramap[_root_.java.time.LocalDate](_.toString) implicit val jsonDecoderApiFraudBanner: Decoder[com.hbc.api.fraud.v0.models.Banner] = Decoder.decodeString.map(com.hbc.api.fraud.v0.models.Banner(_)) implicit val jsonEncoderApiFraudBanner: Encoder[com.hbc.api.fraud.v0.models.Banner] = Encoder.encodeString.contramap[com.hbc.api.fraud.v0.models.Banner](_.toString) implicit val jsonDecoderApiFraudCustomerType: Decoder[com.hbc.api.fraud.v0.models.CustomerType] = Decoder.decodeString.map(com.hbc.api.fraud.v0.models.CustomerType(_)) implicit val jsonEncoderApiFraudCustomerType: Encoder[com.hbc.api.fraud.v0.models.CustomerType] = Encoder.encodeString.contramap[com.hbc.api.fraud.v0.models.CustomerType](_.toString) implicit val jsonDecoderApiFraudRecommendationCode: Decoder[com.hbc.api.fraud.v0.models.RecommendationCode] = Decoder.decodeString.map(com.hbc.api.fraud.v0.models.RecommendationCode(_)) implicit val jsonEncoderApiFraudRecommendationCode: Encoder[com.hbc.api.fraud.v0.models.RecommendationCode] = Encoder.encodeString.contramap[com.hbc.api.fraud.v0.models.RecommendationCode](_.toString) implicit def decodeApiFraudAdditionalData: Decoder[AdditionalData] = Decoder.instance { c => for { inAuthId <- c.downField("inAuthId").as[_root_.java.util.UUID] } yield { AdditionalData( inAuthId = inAuthId ) } } implicit def encodeApiFraudAdditionalData: Encoder[AdditionalData] = Encoder.instance { t => Json.fromFields(Seq( Some("inAuthId" -> t.inAuthId.asJson) ).flatten) } implicit def decodeApiFraudAddress: Decoder[Address] = Decoder.instance { c => for { name <- c.downField("name").as[String] streetLineOne <- c.downField("streetLineOne").as[String] streetLineTwo <- c.downField("streetLineTwo").as[Option[String]] city <- c.downField("city").as[String] state <- c.downField("state").as[String] zip <- c.downField("zip").as[String] country <- c.downField("country").as[String] } yield { Address( name = name, streetLineOne = streetLineOne, streetLineTwo = streetLineTwo, city = city, state = state, zip = zip, country = country ) } } implicit def encodeApiFraudAddress: Encoder[Address] = Encoder.instance { t => Json.fromFields(Seq( Some("name" -> t.name.asJson), Some("streetLineOne" -> t.streetLineOne.asJson), t.streetLineTwo.map(t => "streetLineTwo" -> t.asJson), Some("city" -> t.city.asJson), Some("state" -> t.state.asJson), Some("zip" -> t.zip.asJson), Some("country" -> t.country.asJson) ).flatten) } implicit def decodeApiFraudCreditCard: Decoder[CreditCard] = Decoder.instance { c => for { nameOnCard <- c.downField("nameOnCard").as[String] billingAddress <- c.downField("billingAddress").as[com.hbc.api.fraud.v0.models.Address] cardType <- c.downField("cardType").as[String] cardNumber <- c.downField("cardNumber").as[String] authorizedAmount <- c.downField("authorizedAmount").as[BigDecimal] fullAmount <- c.downField("fullAmount").as[BigDecimal] expiryDate <- c.downField("expiryDate").as[String] authCode <- c.downField("authCode").as[String] responseCode <- c.downField("responseCode").as[Int] merchantId <- c.downField("merchantId").as[Option[Int]] registerNumber <- c.downField("registerNumber").as[Option[String]] transactionNumber <- c.downField("transactionNumber").as[String] avsResponse <- c.downField("avsResponse").as[String] } yield { CreditCard( nameOnCard = nameOnCard, billingAddress = billingAddress, cardType = cardType, cardNumber = cardNumber, authorizedAmount = authorizedAmount, fullAmount = fullAmount, expiryDate = expiryDate, authCode = authCode, responseCode = responseCode, merchantId = merchantId, registerNumber = registerNumber, transactionNumber = transactionNumber, avsResponse = avsResponse ) } } implicit def encodeApiFraudCreditCard: Encoder[CreditCard] = Encoder.instance { t => Json.fromFields(Seq( Some("nameOnCard" -> t.nameOnCard.asJson), Some("billingAddress" -> t.billingAddress.asJson), Some("cardType" -> t.cardType.asJson), Some("cardNumber" -> t.cardNumber.asJson), Some("authorizedAmount" -> t.authorizedAmount.asJson), Some("fullAmount" -> t.fullAmount.asJson), Some("expiryDate" -> t.expiryDate.asJson), Some("authCode" -> t.authCode.asJson), Some("responseCode" -> t.responseCode.asJson), t.merchantId.map(t => "merchantId" -> t.asJson), t.registerNumber.map(t => "registerNumber" -> t.asJson), Some("transactionNumber" -> t.transactionNumber.asJson), Some("avsResponse" -> t.avsResponse.asJson) ).flatten) } implicit def decodeApiFraudCustomer: Decoder[Customer] = Decoder.instance { c => for { customerType <- c.downField("customerType").as[com.hbc.api.fraud.v0.models.CustomerType] accountId <- c.downField("accountId").as[String] creationDate <- c.downField("creationDate").as[_root_.java.time.LocalDate] firstName <- c.downField("firstName").as[String] lastName <- c.downField("lastName").as[String] emailAddress <- c.downField("emailAddress").as[String] } yield { Customer( customerType = customerType, accountId = accountId, creationDate = creationDate, firstName = firstName, lastName = lastName, emailAddress = emailAddress ) } } implicit def encodeApiFraudCustomer: Encoder[Customer] = Encoder.instance { t => Json.fromFields(Seq( Some("customerType" -> t.customerType.asJson), Some("accountId" -> t.accountId.asJson), Some("creationDate" -> t.creationDate.asJson), Some("firstName" -> t.firstName.asJson), Some("lastName" -> t.lastName.asJson), Some("emailAddress" -> t.emailAddress.asJson) ).flatten) } implicit def decodeApiFraudDBRecord: Decoder[DBRecord] = Decoder.instance { c => for { transactionId <- c.downField("transactionId").as[String] lastModifiedDate <- c.downField("lastModifiedDate").as[Option[_root_.java.time.LocalDate]] createdDate <- c.downField("createdDate").as[Option[_root_.java.time.LocalDate]] transaction <- c.downField("transaction").as[com.hbc.api.fraud.v0.models.Transaction] decision <- c.downField("decision").as[com.hbc.api.fraud.v0.models.Decision] } yield { DBRecord( transactionId = transactionId, lastModifiedDate = lastModifiedDate, createdDate = createdDate, transaction = transaction, decision = decision ) } } implicit def encodeApiFraudDBRecord: Encoder[DBRecord] = Encoder.instance { t => Json.fromFields(Seq( Some("transactionId" -> t.transactionId.asJson), t.lastModifiedDate.map(t => "lastModifiedDate" -> t.asJson), t.createdDate.map(t => "createdDate" -> t.asJson), Some("transaction" -> t.transaction.asJson), Some("decision" -> t.decision.asJson) ).flatten) } implicit def decodeApiFraudDecision: Decoder[Decision] = Decoder.instance { c => for { transactionId <- c.downField("transactionId").as[String] recommendationCode <- c.downField("recommendationCode").as[com.hbc.api.fraud.v0.models.RecommendationCode] crossReference <- c.downField("crossReference").as[Option[_root_.java.util.UUID]] rulesTripped <- c.downField("rulesTripped").as[Option[String]] totalScore <- c.downField("totalScore").as[Int] remarks <- c.downField("remarks").as[Option[String]] } yield { Decision( transactionId = transactionId, recommendationCode = recommendationCode, crossReference = crossReference, rulesTripped = rulesTripped, totalScore = totalScore, remarks = remarks ) } } implicit def encodeApiFraudDecision: Encoder[Decision] = Encoder.instance { t => Json.fromFields(Seq( Some("transactionId" -> t.transactionId.asJson), Some("recommendationCode" -> t.recommendationCode.asJson), t.crossReference.map(t => "crossReference" -> t.asJson), t.rulesTripped.map(t => "rulesTripped" -> t.asJson), Some("totalScore" -> t.totalScore.asJson), t.remarks.map(t => "remarks" -> t.asJson) ).flatten) } implicit def decodeApiFraudError: Decoder[Error] = Decoder.instance { c => for { message <- c.downField("message").as[String] error <- c.downField("error").as[String] } yield { Error( message = message, error = error ) } } implicit def encodeApiFraudError: Encoder[Error] = Encoder.instance { t => Json.fromFields(Seq( Some("message" -> t.message.asJson), Some("error" -> t.error.asJson) ).flatten) } implicit def decodeApiFraudItem: Decoder[Item] = Decoder.instance { c => for { svsNumber <- c.downField("svsNumber").as[Int] skuNumber <- c.downField("skuNumber").as[Int] upcNumber <- c.downField("upcNumber").as[Option[Int]] classNumber <- c.downField("classNumber").as[Option[Int]] color <- c.downField("color").as[Option[String]] size <- c.downField("size").as[Option[String]] currentStatus <- c.downField("currentStatus").as[String] department <- c.downField("department").as[Int] signOnDelivery <- c.downField("signOnDelivery").as[Option[String]] giftWrapped <- c.downField("giftWrapped").as[Option[Boolean]] costAmount <- c.downField("costAmount").as[Option[BigDecimal]] marginAmount <- c.downField("marginAmount").as[Option[BigDecimal]] description <- c.downField("description").as[String] price <- c.downField("price").as[BigDecimal] extendedPrice <- c.downField("extendedPrice").as[BigDecimal] quantity <- c.downField("quantity").as[Int] groupNumber <- c.downField("groupNumber").as[Int] shippingAddress <- c.downField("shippingAddress").as[com.hbc.api.fraud.v0.models.Address] shippingMethod <- c.downField("shippingMethod").as[String] shippingCountry <- c.downField("shippingCountry").as[String] } yield { Item( svsNumber = svsNumber, skuNumber = skuNumber, upcNumber = upcNumber, classNumber = classNumber, color = color, size = size, currentStatus = currentStatus, department = department, signOnDelivery = signOnDelivery, giftWrapped = giftWrapped, costAmount = costAmount, marginAmount = marginAmount, description = description, price = price, extendedPrice = extendedPrice, quantity = quantity, groupNumber = groupNumber, shippingAddress = shippingAddress, shippingMethod = shippingMethod, shippingCountry = shippingCountry ) } } implicit def encodeApiFraudItem: Encoder[Item] = Encoder.instance { t => Json.fromFields(Seq( Some("svsNumber" -> t.svsNumber.asJson), Some("skuNumber" -> t.skuNumber.asJson), t.upcNumber.map(t => "upcNumber" -> t.asJson), t.classNumber.map(t => "classNumber" -> t.asJson), t.color.map(t => "color" -> t.asJson), t.size.map(t => "size" -> t.asJson), Some("currentStatus" -> t.currentStatus.asJson), Some("department" -> t.department.asJson), t.signOnDelivery.map(t => "signOnDelivery" -> t.asJson), t.giftWrapped.map(t => "giftWrapped" -> t.asJson), t.costAmount.map(t => "costAmount" -> t.asJson), t.marginAmount.map(t => "marginAmount" -> t.asJson), Some("description" -> t.description.asJson), Some("price" -> t.price.asJson), Some("extendedPrice" -> t.extendedPrice.asJson), Some("quantity" -> t.quantity.asJson), Some("groupNumber" -> t.groupNumber.asJson), Some("shippingAddress" -> t.shippingAddress.asJson), Some("shippingMethod" -> t.shippingMethod.asJson), Some("shippingCountry" -> t.shippingCountry.asJson) ).flatten) } implicit def decodeApiFraudPromo: Decoder[Promo] = Decoder.instance { c => for { promotionCode <- c.downField("promotionCode").as[String] promotionAmount <- c.downField("promotionAmount").as[BigDecimal] } yield { Promo( promotionCode = promotionCode, promotionAmount = promotionAmount ) } } implicit def encodeApiFraudPromo: Encoder[Promo] = Encoder.instance { t => Json.fromFields(Seq( Some("promotionCode" -> t.promotionCode.asJson), Some("promotionAmount" -> t.promotionAmount.asJson) ).flatten) } implicit def decodeApiFraudTransaction: Decoder[Transaction] = Decoder.instance { c => for { orderId <- c.downField("orderId").as[String] groupId <- c.downField("groupId").as[String] orderTotal <- c.downField("orderTotal").as[BigDecimal] shippingCost <- c.downField("shippingCost").as[BigDecimal] currencyCode <- c.downField("currencyCode").as[String] storeNumber <- c.downField("storeNumber").as[Int] salesRep <- c.downField("salesRep").as[Option[String]] lastModifiedDate <- c.downField("lastModifiedDate").as[Option[_root_.java.time.LocalDate]] banner <- c.downField("banner").as[com.hbc.api.fraud.v0.models.Banner] creditCard <- c.downField("creditCard").as[com.hbc.api.fraud.v0.models.CreditCard] promos <- c.downField("promos").as[Option[Seq[com.hbc.api.fraud.v0.models.Promo]]] items <- c.downField("items").as[Seq[com.hbc.api.fraud.v0.models.Item]] ipAddress <- c.downField("ipAddress").as[Option[String]] source <- c.downField("source").as[String] customer <- c.downField("customer").as[com.hbc.api.fraud.v0.models.Customer] additionalData <- c.downField("additionalData").as[Option[com.hbc.api.fraud.v0.models.AdditionalData]] } yield { Transaction( orderId = orderId, groupId = groupId, orderTotal = orderTotal, shippingCost = shippingCost, currencyCode = currencyCode, storeNumber = storeNumber, salesRep = salesRep, lastModifiedDate = lastModifiedDate, banner = banner, creditCard = creditCard, promos = promos, items = items, ipAddress = ipAddress, source = source, customer = customer, additionalData = additionalData ) } } implicit def encodeApiFraudTransaction: Encoder[Transaction] = Encoder.instance { t => Json.fromFields(Seq( Some("orderId" -> t.orderId.asJson), Some("groupId" -> t.groupId.asJson), Some("orderTotal" -> t.orderTotal.asJson), Some("shippingCost" -> t.shippingCost.asJson), Some("currencyCode" -> t.currencyCode.asJson), Some("storeNumber" -> t.storeNumber.asJson), t.salesRep.map(t => "salesRep" -> t.asJson), t.lastModifiedDate.map(t => "lastModifiedDate" -> t.asJson), Some("banner" -> t.banner.asJson), Some("creditCard" -> t.creditCard.asJson), t.promos.map(t => "promos" -> t.asJson), Some("items" -> t.items.asJson), t.ipAddress.map(t => "ipAddress" -> t.asJson), Some("source" -> t.source.asJson), Some("customer" -> t.customer.asJson), t.additionalData.map(t => "additionalData" -> t.asJson) ).flatten) } } } package com.hbc.api.fraud.v0 { import cats.effect._ import cats.implicits._ import io.circe.syntax._ object Constants { val BaseUrl = "http://fraud-frozone" val Namespace = "com.hbc.api.fraud.v0" val UserAgent = "apibuilder app.apibuilder.io/hbc/api-fraud/0.0.2/http4s_0_22" val Version = "0.0.2" val VersionMajor = 0 } class Client[F[_]: Sync]( val baseUrl: org.http4s.Uri = org.http4s.Uri.unsafeFromString("http://fraud-frozone"), auth: scala.Option[com.hbc.api.fraud.v0.Authorization] = None, defaultHeaders: Seq[(String, String)] = Nil, httpClient: org.http4s.client.Client[F] ) extends interfaces.Client[F] { import org.http4s.Response import com.hbc.api.fraud.v0.models.json._ def decisions: Decisions[F] = Decisions def transactions: Transactions[F] = Transactions object Decisions extends Decisions[F] { override def post( decision: com.hbc.api.fraud.v0.models.Decision, requestHeaders: Seq[(String, String)] = Nil ): F[Unit] = { val urlPath = Seq("decision") val (payload, formPayload) = (Some(decision), None) _executeRequest[com.hbc.api.fraud.v0.models.Decision, Unit]("POST", path = urlPath, body = payload, formBody = formPayload, requestHeaders = requestHeaders) { case r if r.status.code == 200 => Sync[F].pure(()) case r if r.status.code == 400 => _root_.com.hbc.api.fraud.v0.Client.parseJson[F, Seq[com.hbc.api.fraud.v0.models.Error]]("Seq[com.hbc.api.fraud.v0.models.Error]", r).flatMap(body => Sync[F].raiseError(new com.hbc.api.fraud.v0.errors.ErrorsResponse(r.headers, r.status.code, None, body))) case r => Sync[F].raiseError(new com.hbc.api.fraud.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 200, 400")) } } } object Transactions extends Transactions[F] { override def post( transaction: com.hbc.api.fraud.v0.models.Transaction, requestHeaders: Seq[(String, String)] = Nil ): F[com.hbc.api.fraud.v0.models.Decision] = { val urlPath = Seq("transaction") val (payload, formPayload) = (Some(transaction), None) _executeRequest[com.hbc.api.fraud.v0.models.Transaction, com.hbc.api.fraud.v0.models.Decision]("POST", path = urlPath, body = payload, formBody = formPayload, requestHeaders = requestHeaders) { case r if r.status.code == 200 => _root_.com.hbc.api.fraud.v0.Client.parseJson[F, com.hbc.api.fraud.v0.models.Decision]("com.hbc.api.fraud.v0.models.Decision", r) case r if r.status.code == 400 => _root_.com.hbc.api.fraud.v0.Client.parseJson[F, Seq[com.hbc.api.fraud.v0.models.Error]]("Seq[com.hbc.api.fraud.v0.models.Error]", r).flatMap(body => Sync[F].raiseError(new com.hbc.api.fraud.v0.errors.ErrorsResponse(r.headers, r.status.code, None, body))) case r => Sync[F].raiseError(new com.hbc.api.fraud.v0.errors.FailedRequest(r.status.code, s"Unsupported response code[${r.status.code}]. Expected: 200, 400")) } } } private lazy val defaultApiHeaders = Seq( ("User-Agent", Constants.UserAgent), ("X-Apidoc-Version", Constants.Version), ("X-Apidoc-Version-Major", Constants.VersionMajor.toString) ) def apiHeaders: Seq[(String, String)] = defaultApiHeaders def modifyRequest(request: org.http4s.Request[F]): org.http4s.Request[F] = request implicit def circeJsonEncoder[F[_]: Sync, A](implicit encoder: io.circe.Encoder[A]) = org.http4s.circe.jsonEncoderOf[F, A] def _executeRequest[T, U]( method: String, path: Seq[String], queryParameters: Seq[(String, String)] = Nil, requestHeaders: Seq[(String, String)] = Nil, body: Option[T] = None, formBody : Option[org.http4s.UrlForm] = None )(handler: org.http4s.Response[F] => F[U] )(implicit encoder: io.circe.Encoder[T]): F[U] = { import org.http4s.QueryParamEncoder._ val m = org.http4s.Method.fromString(method) match { case Right(m) => m case Left(e) => sys.error(e.toString) } val headers = org.http4s.Headers(( apiHeaders ++ defaultHeaders ++ requestHeaders ).groupBy(_._1).map { case (k, l) => org.http4s.Header.Raw(org.typelevel.ci.CIString(k), l.last._2) }.toList) val queryMap = queryParameters.groupBy(_._1).map { case (k, v) => k -> v.map(_._2) } val uri = path.foldLeft(baseUrl){ case (uri, segment) => uri / segment }.setQueryParams(queryMap) val request = org.http4s.Request[F](method = m, uri = uri, headers = headers) val reqAndMaybeAuth = auth.fold(request) { case Authorization.Basic(username, passwordOpt) => { val userpass = s"$username:${passwordOpt.getOrElse("")}" val token = java.util.Base64.getEncoder.encodeToString(userpass.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1)) request.putHeaders(org.http4s.Header.Raw(org.typelevel.ci.CIString("Authorization"), s"Basic $token")) } case a => sys.error("Invalid authorization scheme[" + a.getClass + "]") } val reqAndMaybeAuthAndBody = if (formBody.nonEmpty) formBody.fold(reqAndMaybeAuth)(reqAndMaybeAuth.withEntity) else body.fold(reqAndMaybeAuth)(reqAndMaybeAuth.withEntity) httpClient.fetch(modifyRequest(reqAndMaybeAuthAndBody))(handler) } } object Client { import cats.effect._ implicit def circeJsonDecoder[F[_]: Sync, A](implicit decoder: io.circe.Decoder[A]) = org.http4s.circe.jsonOf[F, A] def parseJson[F[_]: Sync, T]( className: String, r: org.http4s.Response[F] )(implicit decoder: io.circe.Decoder[T]): F[T] = r.attemptAs[T].value.flatMap { case Right(value) => Sync[F].pure(value) case Left(error) => Sync[F].raiseError(new com.hbc.api.fraud.v0.errors.FailedRequest(r.status.code, s"Invalid json for class[" + className + "]", None, error)) } } sealed trait Authorization extends _root_.scala.Product with _root_.scala.Serializable object Authorization { final case class Basic(username: String, password: Option[String] = None) extends Authorization } package interfaces { trait Client[F[_]] { def baseUrl: org.http4s.Uri def decisions: com.hbc.api.fraud.v0.Decisions[F] def transactions: com.hbc.api.fraud.v0.Transactions[F] } } trait Decisions[F[_]] { /** * Consumes the decision about a transaction (usually meant for manual review) and * triggers OMS to process/cancel the order */ def post( decision: com.hbc.api.fraud.v0.models.Decision, requestHeaders: Seq[(String, String)] = Nil ): F[Unit] } trait Transactions[F[_]] { /** * Provides decision about a transaction by supplying order or other information */ def post( transaction: com.hbc.api.fraud.v0.models.Transaction, requestHeaders: Seq[(String, String)] = Nil ): F[com.hbc.api.fraud.v0.models.Decision] } package errors { final case class ErrorsResponse( headers: org.http4s.Headers, status: Int, message: Option[String] = None, body: Seq[com.hbc.api.fraud.v0.models.Error] ) extends Exception(s"HTTP $status${message.fold("")(m => s": $m")}"){ lazy val errors = body } final case class FailedRequest(responseCode: Int, message: String, requestUri: Option[_root_.java.net.URI] = None, parent: Exception = null) extends _root_.java.lang.Exception(s"HTTP $responseCode: $message", parent) } }