-
Notifications
You must be signed in to change notification settings - Fork 989
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MBL-1543: Pledge Redemption payments prototype (#2067)
- Loading branch information
Showing
9 changed files
with
317 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,25 +5,45 @@ import android.os.Build | |
import android.os.Bundle | ||
import android.util.Pair | ||
import android.view.View | ||
import android.widget.Toast | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.viewModels | ||
import androidx.annotation.RequiresApi | ||
import com.kickstarter.R | ||
import com.kickstarter.databinding.PlaygroundLayoutBinding | ||
import com.kickstarter.libs.BaseActivity | ||
import com.kickstarter.libs.RefTag | ||
import com.kickstarter.libs.htmlparser.HTMLParser | ||
import com.kickstarter.libs.htmlparser.TextViewElement | ||
import com.kickstarter.libs.htmlparser.getStyledComponents | ||
import com.kickstarter.libs.qualifiers.RequiresActivityViewModel | ||
import com.kickstarter.libs.utils.extensions.addToDisposable | ||
import com.kickstarter.libs.utils.extensions.getEnvironment | ||
import com.kickstarter.libs.utils.extensions.getPaymentSheetConfiguration | ||
import com.kickstarter.mock.factories.ProjectFactory | ||
import com.kickstarter.models.Project | ||
import com.kickstarter.ui.extensions.showSnackbar | ||
import com.kickstarter.viewmodels.PlaygroundViewModel | ||
import com.kickstarter.viewmodels.PlaygroundViewModel.Factory | ||
import com.stripe.android.ApiResultCallback | ||
import com.stripe.android.PaymentIntentResult | ||
import com.stripe.android.Stripe | ||
import com.stripe.android.StripeIntentResult | ||
import com.stripe.android.paymentsheet.CreateIntentResult | ||
import com.stripe.android.paymentsheet.PaymentSheet | ||
import com.stripe.android.paymentsheet.PaymentSheetResult | ||
import com.stripe.android.paymentsheet.model.PaymentOption | ||
import io.reactivex.android.schedulers.AndroidSchedulers | ||
import io.reactivex.disposables.CompositeDisposable | ||
import kotlinx.coroutines.rx2.asObservable | ||
import timber.log.Timber | ||
|
||
@RequiresActivityViewModel(PlaygroundViewModel.ViewModel::class) | ||
class PlaygroundActivity : BaseActivity<PlaygroundViewModel.ViewModel?>() { | ||
class PlaygroundActivity : ComponentActivity() { | ||
private lateinit var binding: PlaygroundLayoutBinding | ||
private lateinit var view: View | ||
private lateinit var viewModelFactory: Factory | ||
private var stripePaymentMethod: String = "" | ||
val viewModel: PlaygroundViewModel by viewModels { viewModelFactory } | ||
|
||
private lateinit var flowController: PaymentSheet.FlowController | ||
private lateinit var stripeSDK: Stripe | ||
|
||
private val compositeDisposable = CompositeDisposable() | ||
|
||
@RequiresApi(Build.VERSION_CODES.P) | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
|
@@ -32,25 +52,128 @@ class PlaygroundActivity : BaseActivity<PlaygroundViewModel.ViewModel?>() { | |
view = binding.root | ||
setContentView(view) | ||
|
||
val html2 = "<h1>This is heading 1</h1>\n" + | ||
"<h2>This is heading 2</h2>\n" + | ||
"<h3>This is heading 3</h3>\n" + | ||
"<h4>This is heading 4</h4>\n" + | ||
"<h5>This is heading 5</h5>\n" + | ||
"<h6>This is heading 6</h6>" | ||
|
||
val listOfElements = HTMLParser().parse(html2) | ||
|
||
// - The parser detects 6 elements and applies the style to each one | ||
binding.h1.text = (listOfElements[0] as TextViewElement).getStyledComponents(this) | ||
binding.h2.text = (listOfElements[1] as TextViewElement).getStyledComponents(this) | ||
binding.h3.text = (listOfElements[2] as TextViewElement).getStyledComponents(this) | ||
binding.h4.text = (listOfElements[3] as TextViewElement).getStyledComponents(this) | ||
binding.h5.text = (listOfElements[4] as TextViewElement).getStyledComponents(this) | ||
binding.h6.text = (listOfElements[5] as TextViewElement).getStyledComponents(this) | ||
|
||
setStepper() | ||
setStartActivity() | ||
this.getEnvironment()?.let { env -> | ||
viewModelFactory = Factory(env) | ||
stripeSDK = requireNotNull(env.stripe()) | ||
} | ||
|
||
viewModel.payloadUIState.asObservable() | ||
.observeOn(AndroidSchedulers.mainThread()) | ||
.subscribe { | ||
if (it.status == "succeeded") | ||
Toast.makeText(this, "complete_order status: ${it.status}", Toast.LENGTH_LONG) | ||
.show() | ||
if (it.trigger3ds && it.stripePaymentMethodId.isNotEmpty()) { | ||
Toast.makeText( | ||
this, | ||
"complete_order status: ${it.status} triggering 3DS flow", | ||
Toast.LENGTH_LONG | ||
).show() | ||
stripeSDK.handleNextActionForPayment( | ||
this, | ||
clientSecret = it.clientSecret, | ||
) | ||
} | ||
} | ||
.addToDisposable(compositeDisposable) | ||
|
||
flowController = createFlowController() | ||
configureFlowController() | ||
|
||
this.binding.newMethodButton.setOnClickListener { | ||
flowController.presentPaymentOptions() | ||
} | ||
|
||
this.binding.pledgeButton.setOnClickListener { | ||
flowController.confirm() | ||
} | ||
} | ||
|
||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { | ||
super.onActivityResult(requestCode, resultCode, intent) | ||
stripeSDK.onPaymentResult( | ||
requestCode, intent, | ||
object : ApiResultCallback<PaymentIntentResult> { | ||
override fun onSuccess(result: PaymentIntentResult) { | ||
if (result.outcome == StripeIntentResult.Outcome.SUCCEEDED) { | ||
if (result.intent.requiresAction() == false) { | ||
Toast.makeText(this@PlaygroundActivity, "PaymentIntent: ${result.intent.id} requiresAction: ${result.intent.requiresAction()} 3DS flow Outcome = SUCCEEDED", Toast.LENGTH_LONG) | ||
.show() | ||
} | ||
} | ||
} | ||
|
||
override fun onError(e: Exception) { | ||
Toast.makeText(this@PlaygroundActivity, " 3DS flow StripeIntentResult.Outcome = ERRORED", Toast.LENGTH_LONG) | ||
.show() | ||
} | ||
} | ||
) | ||
} | ||
|
||
private fun createFlowController() = PaymentSheet.FlowController.create( | ||
activity = this, | ||
paymentOptionCallback = ::onPaymentOption, | ||
createIntentCallback = { paymentMethod, _ -> | ||
// - createIntentCallback is triggered with flowController.confirm() | ||
// - Make a request to complete to create a PaymentIntent and return its client secret | ||
try { | ||
viewModel.completeOrder(paymentMethod.id ?: "") | ||
viewModel.payloadUIState.value.apply { | ||
if (this.status == "succeeded") { | ||
CreateIntentResult.Success(this.clientSecret) | ||
} | ||
} | ||
viewModel.payloadUIState.collect { | ||
} | ||
} catch (e: Exception) { | ||
Toast.makeText(this, "error when calling complete_order", Toast.LENGTH_LONG).show() | ||
CreateIntentResult.Failure( | ||
cause = e, | ||
displayMessage = e.message | ||
) | ||
} | ||
}, | ||
paymentResultCallback = ::onPaymentSheetResult, | ||
) | ||
|
||
private fun onPaymentOption(paymentOption: PaymentOption?) { | ||
paymentOption?.let { | ||
val toast = Toast.makeText(this, "new payment added: ${paymentOption.label}", Toast.LENGTH_LONG) // in Activity | ||
toast.show() | ||
Timber.d("paymentOption: $paymentOption") | ||
} | ||
} | ||
|
||
private fun configureFlowController() { | ||
flowController.configureWithIntentConfiguration( | ||
intentConfiguration = PaymentSheet.IntentConfiguration( | ||
mode = PaymentSheet.IntentConfiguration.Mode.Payment( | ||
amount = 1099, | ||
currency = "usd", | ||
), | ||
), | ||
// onBehalfOf = "acct_1Ir6hZ4NJG33TWAg", | ||
configuration = this.getPaymentSheetConfiguration("[email protected]"), | ||
callback = { success, error -> | ||
}, | ||
) | ||
} | ||
|
||
fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) { | ||
when (paymentSheetResult) { | ||
is PaymentSheetResult.Canceled -> { | ||
// Customer canceled - you should probably do nothing. | ||
} | ||
is PaymentSheetResult.Failed -> { | ||
print("Error: ${paymentSheetResult.error}") | ||
// PaymentSheet encountered an unrecoverable error. You can display the error to the user, log it, etc. | ||
} | ||
is PaymentSheetResult.Completed -> { | ||
// Display, for example, an order confirmation screen | ||
print("Completed") | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
26 changes: 0 additions & 26 deletions
26
app/src/internal/java/com/kickstarter/viewmodels/PlaygroundViewModel.java
This file was deleted.
Oops, something went wrong.
77 changes: 77 additions & 0 deletions
77
app/src/internal/java/com/kickstarter/viewmodels/PlaygroundViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package com.kickstarter.viewmodels | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.lifecycle.viewModelScope | ||
import com.kickstarter.libs.Environment | ||
import com.kickstarter.models.CompleteOrderInput | ||
import com.kickstarter.models.CompleteOrderPayload | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.rx2.asFlow | ||
|
||
class PlaygroundViewModel(environment: Environment) : ViewModel() { | ||
|
||
private val apolloClient = requireNotNull(environment.apolloClientV2()) | ||
|
||
private var _payloadFlow = MutableStateFlow(CompleteOrderPayload()) | ||
val payloadUIState: StateFlow<CompleteOrderPayload> = _payloadFlow.asStateFlow() | ||
|
||
private val _stripePaymentMethodId = MutableStateFlow<String>("") | ||
val stripePaymentMethodId: StateFlow<String> = _stripePaymentMethodId.asStateFlow() | ||
|
||
fun completeOrder(stripeId: String) { | ||
viewModelScope.launch { | ||
val input = CompleteOrderInput( | ||
projectId = "UHJvamVjdC01NzYyNDQ0OTk=", | ||
stripePaymentMethodId = stripeId, | ||
) | ||
|
||
apolloClient.completeOrder(input).asFlow() | ||
.collect { | ||
val response = when (it.status) { | ||
"requires_action" -> { | ||
_payloadFlow.emit( | ||
CompleteOrderPayload( | ||
status = it.status, | ||
clientSecret = it.clientSecret, | ||
trigger3ds = true, | ||
stripePaymentMethodId = stripeId | ||
) | ||
) | ||
} | ||
|
||
"succeeded" -> { | ||
_payloadFlow.emit( | ||
CompleteOrderPayload( | ||
status = it.status, | ||
clientSecret = it.clientSecret, | ||
trigger3ds = false, | ||
stripePaymentMethodId = stripeId | ||
) | ||
) | ||
} | ||
|
||
else -> { | ||
_payloadFlow.emit( | ||
CompleteOrderPayload( | ||
status = "error", | ||
clientSecret = it.clientSecret, | ||
trigger3ds = false, | ||
stripePaymentMethodId = stripeId | ||
) | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
class Factory(private val environment: Environment) : ViewModelProvider.Factory { | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
return PlaygroundViewModel(environment) as T | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.kickstarter.models | ||
|
||
/** | ||
* Data model for complete order mutation request | ||
*/ | ||
data class CompleteOrderInput( | ||
val projectId: String, | ||
val orderId: String? = null, | ||
val stripePaymentMethodId: String? = null, | ||
val paymentSourceId: String? = null, | ||
val paymentSourceReusable: Boolean? = null, | ||
val paymentMethodTypes: List<String>? = null | ||
) | ||
|
||
/** | ||
* Data model for complete order mutation response | ||
*/ | ||
data class CompleteOrderPayload( | ||
val status: String = "", | ||
val clientSecret: String = "", | ||
val trigger3ds: Boolean = false, | ||
val stripePaymentMethodId: String = "" | ||
) |
Oops, something went wrong.