Skip to content

Commit

Permalink
Contract changes (#12)
Browse files Browse the repository at this point in the history
* Update Destination to Target

* Be a little more permissive in BankId names

* More flexibility for etrade

* Adding MakePurchase

* Add makePurchase

* Update all server endpoints
  • Loading branch information
DamianReeves authored Sep 30, 2024
1 parent e060d06 commit dd85e13
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 29 deletions.
8 changes: 8 additions & 0 deletions api/src-elm/BankerX/Banks/CapitalOne.elm
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ getPoints vendor amount =
4 * amount
else
amount

makePurchase: Purchase -> Provider
makePurchase purchase =
let
terms: Terms
terms = getTerms purchase
in
terms.provider
8 changes: 8 additions & 0 deletions api/src-elm/BankerX/Banks/Etrade.elm
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ getPoints vendor amount =
4 * amount
else
amount

makePurchase: Purchase -> Provider
makePurchase purchase =
let
terms: Terms
terms = getTerms purchase
in
terms.provider
10 changes: 9 additions & 1 deletion api/src-elm/BankerX/Banks/Klarna.elm
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ getPoints category amount =
else if List.member category [Home] then
2 * amount
else
amount
amount

makePurchase: Purchase -> Provider
makePurchase purchase =
let
terms: Terms
terms = getTerms purchase
in
terms.provider
36 changes: 29 additions & 7 deletions api/src-elm/BankerX/SmartWallet.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,40 @@ import BankerX.Banks.Klarna as Klarna

{-| Service definition for the SmartWallet service -}
type alias Service =
{ getTerms: BankID -> Purchase -> Maybe Terms}
{ getTerms: BankID -> Purchase -> Maybe Terms
, makePurchase: BankID -> Purchase -> Maybe Provider
}

{-| Service implementation for the SmartWallet service -}
service : Service
service =
{ getTerms = getTerms }
{ getTerms = getTerms
, makePurchase = makePurchase
}

{-| Get the terms for a purchase from a bank -}
getTerms : BankID -> Purchase -> Maybe Terms
getTerms bankId purchase =
case bankId of
"CapitalOne" -> Just(CapitalOne.getTerms purchase)
"E*trade" -> Just(Etrade.getTerms purchase)
"Klarna" -> Just(Klarna.getTerms purchase)
_ -> Nothing
let
normalizedBankId:String
normalizedBankId = String.toLower bankId
in
case normalizedBankId of
"capitalone" -> Just(CapitalOne.getTerms purchase)
"e*trade" -> Just(Etrade.getTerms purchase)
"etrade" -> Just(Etrade.getTerms purchase)
"klarna" -> Just(Klarna.getTerms purchase)
_ -> Nothing

makePurchase: BankID -> Purchase -> Maybe Provider
makePurchase bankId purchase =
let
normalizedBankId:String
normalizedBankId = String.toLower bankId
in
case normalizedBankId of
"capitalone" -> Just(CapitalOne.makePurchase purchase)
"e*trade" -> Just(Etrade.makePurchase purchase)
"etrade" -> Just(Etrade.makePurchase purchase)
"klarna" -> Just(Klarna.makePurchase purchase)
_ -> Nothing
16 changes: 16 additions & 0 deletions api/src/bankerx/api/PublicEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,19 @@ object PublicEndpoints:
.in(jsonBody[GetTermsIntent])
.out(jsonBody[GetTermsResponsePayload])
.errorOut(stringBody)

val makePurchaseEndpoint: PublicEndpoint[
(BankName, MakePurchaseIntent),
String,
MakePurchaseResponse,
Any
] =
endpoint.post
.in(
"api" / "fdc3" / "bank" / path[BankID](
"bank"
) / "intents" / "make-purchase"
)
.in(jsonBody[MakePurchaseIntent])
.out(jsonBody[MakePurchaseResponse])
.errorOut(stringBody)
27 changes: 26 additions & 1 deletion api/src/bankerx/api/fdc3/Fdc3Service.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package bankerx.api.fdc3
import bankerx.API.{BankID, Terms}
import bankerx.API.*
import bankerx.SmartWallet

trait Fdc3Service:
Expand All @@ -8,6 +8,12 @@ trait Fdc3Service:
getTermsIntent: GetTermsIntent
): Either[String, GetTermsResponsePayload]

def makePurchase(
bankId: BankID,
makePurchaseIntent: MakePurchaseIntent
): Either[String, MakePurchaseResponse]
end Fdc3Service

object Fdc3Service:
case object Live extends Fdc3Service:
def getTerms(
Expand All @@ -20,3 +26,22 @@ object Fdc3Service:
.fold(Left(s"Terms unavailable for bank: $bankId"))((terms: Terms) =>
Right(GetTermsResponsePayload(PayloadType.Fdc3Terms, terms))
)

def makePurchase(
bankId: BankID,
makePurchaseIntent: MakePurchaseIntent
): Either[String, MakePurchaseResponse] =
val purchase = makePurchaseIntent.context.data
SmartWallet
.makePurchase(bankId)(purchase)
.fold(Left(s"Purchase failed for bank: $bankId"))(
(provider: Provider) =>
Right(
MakePurchaseResponse(
PayloadType.Fdc3PurchaseConfirmation,
MakePurchaseConfirmation(provider)
)
)
)
end Live
end Fdc3Service
55 changes: 44 additions & 11 deletions api/src/bankerx/api/fdc3/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker.*

type IntentName = IntentName.Type
object IntentName extends Subtype[String]:
val purchase = IntentName("fdc3.purchase")
def makeOption(input: String): Option[IntentName] = make(input).toOption

given tapirSchema: Schema[IntentName] =
Expand All @@ -31,18 +32,19 @@ object Source extends Subtype[String]:
subtypeCodec[String, Source]
extension (source: Source) def value: String = source

type Destination = Destination.Type
object Destination extends Subtype[String]:
def makeOption(input: String): Option[Destination] = make(input).toOption
given tapirSchema: Schema[Destination] =
Schema.string.map[Destination](makeOption)(_.value)
given jsonValueCodec: JsonValueCodec[Destination] =
subtypeCodec[String, Destination]
extension (destination: Destination) def value: String = destination
type Target = Target.Type
object Target extends Subtype[String]:
def makeOption(input: String): Option[Target] = make(input).toOption
given tapirSchema: Schema[Target] =
Schema.string.map[Target](makeOption)(_.value)
given jsonValueCodec: JsonValueCodec[Target] =
subtypeCodec[String, Target]
extension (destination: Target) def value: String = destination

type PayloadType = PayloadType.Type
object PayloadType extends Subtype[String]:
val Fdc3Terms = PayloadType("fdc3.terms")
val Fdc3PurchaseConfirmation = PayloadType("fdc3.purchaseConfirmation")

def makeOption(input: String): Option[PayloadType] = make(input).toOption
given tapirSchema: Schema[PayloadType] =
Expand All @@ -54,8 +56,8 @@ object PayloadType extends Subtype[String]:
trait Fdc3Intent[+Data] extends Product with Serializable:
def intent: IntentName
def source: Source
def destination: Destination
// def context: Fdc3Payload[Data]
def target: Target
def context: Fdc3Payload[Data]

trait Fdc3Payload[+Data] extends Product with Serializable:
def `type`: PayloadType
Expand All @@ -64,7 +66,7 @@ trait Fdc3Payload[+Data] extends Product with Serializable:
final case class GetTermsIntent(
intent: IntentName,
source: Source,
destination: Destination,
target: Target,
context: GetTermsRequestPayload
) extends Fdc3Intent[Purchase]
object GetTermsIntent:
Expand All @@ -81,6 +83,37 @@ object GetTermsResponsePayload:
given jsonValueCodec: JsonValueCodec[GetTermsResponsePayload] =
JsonCodecMaker.make

final case class MakePurchaseConfirmation(
provider: Provider
)

object MakePurchaseConfirmation:
given tapirSchema: Schema[MakePurchaseConfirmation] = Schema.derived
given jsonValueCodec: JsonValueCodec[MakePurchaseConfirmation] =
JsonCodecMaker.make

final case class MakePurchaseIntent(
intent: IntentName,
source: Source,
target: Target,
context: GetTermsRequestPayload
) extends Fdc3Intent[Purchase]

object MakePurchaseIntent:
given tapirSchema: Schema[MakePurchaseIntent] = Schema.derived
given jsonValueCodec: JsonValueCodec[MakePurchaseIntent] =
JsonCodecMaker.make

final case class MakePurchaseResponse(
`type`: PayloadType,
data: MakePurchaseConfirmation
) extends Fdc3Payload[MakePurchaseConfirmation]

object MakePurchaseResponse:
given tapirSchema: Schema[MakePurchaseResponse] = Schema.derived
given jsonValueCodec: JsonValueCodec[MakePurchaseResponse] =
JsonCodecMaker.make

final case class GetTermsRequestPayload(
`type`: PayloadType,
data: Purchase
Expand Down
4 changes: 2 additions & 2 deletions api/test/src/bankerx/api/CodecsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ class CodecsSpec extends AnyWordSpec with should.Matchers with Codecs:
"given a GetTermsIntent" should {
"be able to encode and decode it" in {
val getTermsIntent = fdc3.GetTermsIntent(
fdc3.IntentName("intent1"),
fdc3.IntentName.purchase,
fdc3.Source("source1"),
fdc3.Destination("destination1"),
fdc3.Target("destination1"),
fdc3.GetTermsRequestPayload(
fdc3.PayloadType("payloadType1"),
Purchase(
Expand Down
14 changes: 9 additions & 5 deletions cdk/lib/bankerx-cdk-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,17 @@ export class BankerXCdkStack extends cdk.Stack {
rootApiBankerTermsBankName.addResource("terms");
rootApiBankerTermsBankNameTerms.addMethod("POST");

// Create a resource for performing a POST to /api/fdc3/bank/{bankName}/intents/terms
rootApi
// Create a resource for performing a POST to /api/fdc3/bank/{bankName}/intents
const intentsApiRoot = rootApi
.addResource("fdc3")
.addResource("bank")
.addResource("{bankName}")
.addResource("intents")
.addResource("get-terms")
.addMethod("POST");
.addResource("intents");

// Create a resource for performing a POST to /api/fdc3/bank/{bankName}/intents/get-terms
intentsApiRoot.addResource("get-terms").addMethod("POST");

// Create a resource for performing a POST to /api/fdc3/bank/{bankName}/intents/make-purchase
intentsApiRoot.addResource("make-purchase").addMethod("POST");
}
}
7 changes: 6 additions & 1 deletion server/src/bankerx/server/ServerEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ object ServerEndpoints:
case (bankName, getTermsIntent) => Fdc3Service.Live.getTerms(bankName, getTermsIntent)
}

val apiEndpoints = List(getTermsServerEndpoint)
val makePurchaseServerEndpoint =
PublicEndpoints.fdc3.makePurchaseEndpoint.handle{
case (bankName, makePurchaseIntent) => Fdc3Service.Live.makePurchase(bankName, makePurchaseIntent)
}

val apiEndpoints = List(getTermsServerEndpoint, makePurchaseServerEndpoint)
11 changes: 10 additions & 1 deletion serverless/src/bankerx/serverless/ServerlessEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,14 @@ object ServerlessEndpoints extends ZTapir:
ZIO.fromEither(result)
}

val apiEndpoints: Set[ZioEndpoint] = Set(getTermsServerEndpoint)
val makePurchaseServerEndpoint: ZioEndpoint =
PublicEndpoints.fdc3.makePurchaseEndpoint
.zServerLogic { case (bankName, makePurchaseIntent) =>
val result =
Fdc3Service.Live.makePurchase(bankName, makePurchaseIntent)
ZIO.fromEither(result)
}

val apiEndpoints: Set[ZioEndpoint] =
Set(getTermsServerEndpoint, makePurchaseServerEndpoint)
val allEndpoints: Set[ZioEndpoint] = apiEndpoints

0 comments on commit dd85e13

Please sign in to comment.