From 81d14a60df0945f38cf71e1ed1e10af04a4a423e Mon Sep 17 00:00:00 2001 From: mtgriego Date: Wed, 21 Feb 2024 13:06:39 -0800 Subject: [PATCH] add query and migrations for checkout flow (#1953) --- app/src/main/graphql/checkout.graphql | 21 +++- .../mock/services/MockApolloClient.kt | 17 ++++ .../models/CreatePaymentIntentInput.kt | 2 +- .../models/PaymentValidationResponse.kt | 3 + .../kickstarter/services/KSApolloClientV2.kt | 95 ++++++++++++++++++- 5 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/kickstarter/models/PaymentValidationResponse.kt diff --git a/app/src/main/graphql/checkout.graphql b/app/src/main/graphql/checkout.graphql index d45861b10b..58727bb6b4 100644 --- a/app/src/main/graphql/checkout.graphql +++ b/app/src/main/graphql/checkout.graphql @@ -49,8 +49,25 @@ mutation CreateCheckout($projectId: ID!, $amount: String!, $rewardIds: [ID!], $l } } -mutation CreatePaymentIntent($projectId: ID!, $amountDollars: String!) { - createPaymentIntent(input: { projectId: $projectId, amount: $amountDollars } ) { +mutation CreatePaymentIntent($projectId: ID!, $amount: String!) { + createPaymentIntent(input: { projectId: $projectId, amount: $amount } ) { clientSecret } } + +query ValidateCheckout($checkoutId:ID!, $paymentSourceId:String!, $paymentIntentClientSecret:String!) { + checkout(id: $checkoutId) { + isValidForOnSessionCheckout(stripePaymentMethodId: $paymentSourceId, paymentIntentClientSecret: $paymentIntentClientSecret) { + valid + messages + } + } +} + +mutation CompleteOnSessionCheckout($checkoutId: ID!, $paymentIntentClientSecret: String!, $paymentSourceId: String) { + completeOnSessionCheckout(input:{ checkoutId: $checkoutId, paymentIntentClientSecret: $paymentIntentClientSecret, paymentSourceId: $paymentSourceId } ) { + checkout { + id + } + } +} diff --git a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt index e06cc1358a..b74f9d2862 100644 --- a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt +++ b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt @@ -31,6 +31,7 @@ import com.kickstarter.models.CreatePaymentIntentInput import com.kickstarter.models.CreatorDetails import com.kickstarter.models.ErroredBacking import com.kickstarter.models.Location +import com.kickstarter.models.PaymentValidationResponse import com.kickstarter.models.Project import com.kickstarter.models.Reward import com.kickstarter.models.StoredCard @@ -275,6 +276,22 @@ open class MockApolloClientV2 : ApolloClientTypeV2 { override fun createPaymentIntent(createPaymentIntentInput: CreatePaymentIntentInput): io.reactivex.Observable { return io.reactivex.Observable.empty() } + + override fun validateCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): io.reactivex.Observable { + return io.reactivex.Observable.empty() + } + + override fun completeOnSessionCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): io.reactivex.Observable { + return io.reactivex.Observable.empty() + } } open class MockApolloClient : ApolloClientType { diff --git a/app/src/main/java/com/kickstarter/models/CreatePaymentIntentInput.kt b/app/src/main/java/com/kickstarter/models/CreatePaymentIntentInput.kt index 699de02b6e..bd611216d3 100644 --- a/app/src/main/java/com/kickstarter/models/CreatePaymentIntentInput.kt +++ b/app/src/main/java/com/kickstarter/models/CreatePaymentIntentInput.kt @@ -1,3 +1,3 @@ package com.kickstarter.models -data class CreatePaymentIntentInput(val project: Project, val amountDollars: String) +data class CreatePaymentIntentInput(val project: Project, val amount: String) diff --git a/app/src/main/java/com/kickstarter/models/PaymentValidationResponse.kt b/app/src/main/java/com/kickstarter/models/PaymentValidationResponse.kt new file mode 100644 index 0000000000..953393346e --- /dev/null +++ b/app/src/main/java/com/kickstarter/models/PaymentValidationResponse.kt @@ -0,0 +1,3 @@ +package com.kickstarter.models + +data class PaymentValidationResponse(val isValid: Boolean, val messages: List) diff --git a/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt b/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt index 0f34a30526..d6e2805f31 100644 --- a/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt +++ b/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt @@ -1,6 +1,8 @@ package com.kickstarter.services +import CompleteOnSessionCheckoutMutation import CreatePaymentIntentMutation +import ValidateCheckoutQuery import android.util.Pair import com.apollographql.apollo.ApolloCall import com.apollographql.apollo.ApolloClient @@ -16,6 +18,7 @@ import com.kickstarter.models.CreatePaymentIntentInput import com.kickstarter.models.CreatorDetails import com.kickstarter.models.ErroredBacking import com.kickstarter.models.Location +import com.kickstarter.models.PaymentValidationResponse import com.kickstarter.models.Project import com.kickstarter.models.Reward import com.kickstarter.models.StoredCard @@ -132,6 +135,18 @@ interface ApolloClientTypeV2 { fun createCheckout(createCheckoutData: CreateCheckoutData): Observable fun createPaymentIntent(createPaymentIntentInput: CreatePaymentIntentInput): Observable + + fun validateCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): Observable + + fun completeOnSessionCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): Observable } private const val PAGE_SIZE = 25 @@ -1418,11 +1433,11 @@ class KSApolloClientV2(val service: ApolloClient) : ApolloClientTypeV2 { checkoutObj.paymentUrl() ) ps.onNext(checkout) - ps.onComplete() } ?: ps.onError(Exception("CreateCheckout could not decode ID")) } } } + ps.onComplete() } }) return@defer ps @@ -1436,7 +1451,7 @@ class KSApolloClientV2(val service: ApolloClient) : ApolloClientTypeV2 { this.service.mutate( CreatePaymentIntentMutation.builder() .projectId(encodeRelayId(createPaymentIntentInput.project)) - .amountDollars(createPaymentIntentInput.amountDollars) + .amount(createPaymentIntentInput.amount) .build() ).enqueue(object : ApolloCall.Callback() { override fun onFailure(e: ApolloException) { @@ -1447,7 +1462,81 @@ class KSApolloClientV2(val service: ApolloClient) : ApolloClientTypeV2 { if (response.hasErrors()) { ps.onError(Exception(response.errors?.first()?.message)) } else { - ps.onNext(response.data?.createPaymentIntent()?.clientSecret() ?: "") + response.data?.createPaymentIntent()?.clientSecret()?.let { + ps.onNext(it) + } ?: ps.onError(Exception("Client Secret was Null")) + } + ps.onComplete() + } + }) + return@defer ps + } + } + + override fun validateCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): Observable { + return Observable.defer { + val ps = PublishSubject.create() + + this.service.query( + ValidateCheckoutQuery.builder() + .checkoutId(checkoutId) + .paymentIntentClientSecret(paymentIntentClientSecret) + .paymentSourceId(paymentSourceId) + .build() + ).enqueue(object : ApolloCall.Callback() { + override fun onFailure(e: ApolloException) { + ps.onError(e) + } + + override fun onResponse(response: Response) { + if (response.hasErrors()) { + ps.onError(Exception(response.errors?.first()?.message)) + } else { + response.data?.let { data -> + val validation = PaymentValidationResponse( + data.checkout()?.isValidForOnSessionCheckout?.valid() ?: false, + data.checkout()?.isValidForOnSessionCheckout?.messages() ?: listOf() + ) + ps.onNext(validation) + } + ps.onComplete() + } + } + }) + return@defer ps + } + } + + override fun completeOnSessionCheckout( + checkoutId: String, + paymentIntentClientSecret: String, + paymentSourceId: String + ): Observable { + return Observable.defer { + val ps = PublishSubject.create() + + this.service.mutate( + CompleteOnSessionCheckoutMutation.builder() + .checkoutId(checkoutId) + .paymentIntentClientSecret(paymentIntentClientSecret) + .paymentSourceId(paymentSourceId) + .build() + ).enqueue(object : ApolloCall.Callback() { + override fun onFailure(e: ApolloException) { + ps.onError(e) + } + + override fun onResponse(response: Response) { + if (response.hasErrors()) { + ps.onError(Exception(response.errors?.first()?.message)) + } else { + response.data?.completeOnSessionCheckout()?.checkout()?.id()?.let { + ps.onNext(it) + } ?: ps.onError(Exception("Checkout ID was null")) } ps.onComplete() }