#emilien_android-question

1 messages · Page 1 of 1 (latest)

night ospreyBOT
#

👋 Welcome to your new thread!

⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1291417939622232074

📝 Have more to share? Add more details, code, screenshots, videos, etc. below.

ebon epoch
#

To me more precise,

I want to collect Stripe cards information with the UI SDK.

Then map the Stripe Model to my Domain Model (to be agnostic from Stripe modelin my domain).
Then, I provide a Port CreatePaymentMethod
In the implementation of this port,

I want to do sequencially PostSetupIntent, and then confirm the SetupIntent with params recreated from my domain model.
For the PostSetupIntent, i will still call my own backend for getting the secret. And then, I call Stripe.confirmSetupIntent() from the Stripe SDK.
If every steps is correct, i display a payment created dialog on my UI.

Is this okey to do this ?

  • Using CardInputWidget to collect CardParams. Then mapping CardParams in my domain model with toParamMap() (because Card is internal and i Can't access his fields).
languid scroll
#

You can structure your app however you want. You just can't handle raw card details yourself

#

So you need to use our components

ebon epoch
#

So I'm forced to proceed with Stripe model in my domain ?

languid scroll
#

I don't really understand what you mean by the above

#

But you cannot handle raw card details yourself unless you are pci compliant

#

it's illegal

ebon epoch
#

Mmh, my problem is that we are forced to confirm a setup intent from the UI, not even in a view model

#

And in a DDD concern, I don’t have any business logic representing a stripe setup intent,

It lead that my app and my business his coupled with Stripe

#

If I want to leave Stripe I will have to rebuild my business logic regarding payment

languid scroll
#

I don't really understand what you're asking

#

I'd need an example or something

ebon epoch
#

Yes, I’ll be in the train in 15min I’ll try to be more concise

night ospreyBOT
signal cairn
#

emilien_android-question

ebon epoch
#

Okey,

I have an AndroidView that is using stripe component to collect card infos.
AndroidView(
modifier = Modifier.border(1.dp, RiderTheme.colorScheme.tertiary, RoundedCornerShape(4.dp)),
factory = { context ->
CardInputWidget(context).apply {
this.postalCodeRequired = false
this.postalCodeEnabled = false

                    this.setCardValidCallback { isValid, invalidFields ->
                        onEvent(CreatePaymentMethodEvent.OnCardValid(isValid, this.paymentMethodCreateParams?.card, invalidFields))
                    }
                }
            }
        )
#

then, when the user click register, i want to create the payment method.
fun onClickRegister() {
_state.update { it.copy(isLoading = true) }
viewModelScope.launch {
paymentMethodState.value.paymentMethodCreateParams?.let { payment ->
val accessibleCard = createPaymentMethodFromParamMap(payment.toParamMap())
val paymentIntent = CreatePaymentMethodIntent(
ownerName = paymentMethodState.value.owner.text.toString(),
label = paymentMethodState.value.name.text.toString(),
code = accessibleCard.number,
cvc = accessibleCard.cvc,
expiryMonth = accessibleCard.expiryMonth,
expiryYear = accessibleCard.expiryYear,
email = state.value.emailTextFieldState.text.toString()
)
val result = createPersonalProfile.invoke(paymentIntent)
}
}

As i'm working in a clean architecture app, when i call my CreatePersonalProfile use case, i cannot use Stripe Card model to transfer data.

#

private fun createPaymentMethodFromParamMap(map: Map<String, Any>): CreatePaymentMethodUiModel {
return CreatePaymentMethodUiModel(
expiryMonth = map["exp_month"] as Int,
expiryYear = map["exp_year"] as Int,
number = map["number"] as String,
cvc = map["cvc"] as String,
)
}
That why i'm mapping Stripe card model in my domain model

#

then, my CreatePersonaProfileUseCase in calling an implementation :

override suspend fun createPaymentMethod(
createPaymentMethodIntent: CreatePaymentMethodIntent,
paymentProvider: PaymentProvider,
): ResultOf<PaymentMethod> {
if (idempotencyKey == null) {
idempotencyKey = System.currentTimeMillis().toHexString()
}

    val paymentConfig = PaymentConfiguration.getInstance(context)

    val stripe = Stripe(
        context = context,
        publishableKey = paymentConfig.publishableKey,
        stripeAccountId = paymentConfig.stripeAccountId
    )

    if(setupIntentSecret == null)
        setupIntentSecret = postStripeSetupIntent(paymentProvider.id)

    val card = buildCard(createPaymentMethodIntent)
    val billingDetails = com.stripe.android.model.PaymentMethod.BillingDetails.Builder().apply {
        setEmail(createPaymentMethodIntent.email)
        setName(createPaymentMethodIntent.ownerName)
    }.build()

    val paymentMethod: PaymentMethod?

    try {
        paymentMethod = confirmSetupIntent(
            stripe,
            card,
            createPaymentMethodIntent.label,
            billingDetails,
            setupIntentSecret!!
        )
    } catch (e: AuthenticationException) {
        return ResultOf.Error(e)
    } catch (e: APIConnectionException) {
        return ResultOf.Error(e)
    } catch (e: APIException) {
        return ResultOf.Error(e)
    } catch (e: InvalidRequestException) {
        return ResultOf.Error(e)
    } catch (e: CancellationException){
        throw e
    } catch (e: Exception) {
        return ResultOf.Error(e)
    }
    idempotencyKey = null
    setupIntentSecret = null


    return ResultOf.Success(paymentMethod)
}

where i'm recreating the Stripe Model to pass it to Stripe SDK

#

private suspend fun confirmSetupIntent(
stripe: Stripe,
card: PaymentMethodCreateParams.Card,
cardName: String,
billingDetails: com.stripe.android.model.PaymentMethod.BillingDetails,
setupIntent: String,
): PaymentMethod {
val result = stripe.confirmSetupIntent(
confirmSetupIntentParams = ConfirmSetupIntentParams.create(
paymentMethodCreateParams = PaymentMethodCreateParams.create(
card,
billingDetails,
mapOf(Pair("payment_method_label", cardName))
),
clientSecret = setupIntent
),
idempotencyKey = idempotencyKey,
expand = listOf("payment_method")
)

    val stripePM = result.paymentMethod!!
    return PaymentMethod(
        stripePM.id ?: "",
        stripePM.card?.expiryYear ?: 0,
        stripePM.card?.expiryMonth ?: 0,
        stripePM.card?.last4 ?: "",
        stripePM.billingDetails?.name ?: "",
        false,
        brand = stripePM.card?.brand?.name ?: ""
    )
}

private fun buildCard(createPaymentMethodIntent: CreatePaymentMethodIntent): PaymentMethodCreateParams.Card {
    val builder = PaymentMethodCreateParams.Card.Builder()
    builder.setCvc(createPaymentMethodIntent.cvc)
    builder.setExpiryMonth(createPaymentMethodIntent.expiryMonth)
    builder.setExpiryYear(createPaymentMethodIntent.expiryYear)
    builder.setNumber(createPaymentMethodIntent.code)

    val card = builder.build()
    return card
}
#

You may ask why I don't follow the initial tutorial i Was following:
With this tutorial, I'm forced to have Stripe implementation details in my domain layer --> This is violating clean architecture principals

#
  • it's annoying to have to register the paymentLauncher, observing the success of setupIntent then, build the confirm params, then confirm setupIntent from launcher finally handle result
outer linden
#

Hi 👋

I'm stepping in as I have some experience with our Android integrations. Give me a moment to review what you've shared so far.

ebon epoch
#

So i've tried to build this "clean architecture" approach but I'm not sure that this is compliant

ebon epoch
outer linden
#

Happy to help. Right now I'm reading the thread top to bottom so give me a minute. Thanks

#

Hmmm... okay I think I get the problem you are hitting here but I'm still confused about the key point my colleague raised. Are you storing the card details (number, expiration, cvc)? Or just keeping them in the view model until you use the Setup Intent?

#

I understand the desire to build a processor agnostic approach but I think you will still run into difficulty complying with PCI regulations. Stripe has build our components to be more tightly coupled between the collection of information and the creating of Stripe Payment Method objects in order to reduce the PCI compliance burden for you

ebon epoch
#

Just keeping them in memory until I confirm the setup Intent.

They are never stored in any local database neither in the backend server

outer linden
#

Okay so, is there a specific step in the tutorial where you are hitting a roadblock?

ebon epoch
#

I’m riding on my bicycle 10min to get back home

outer linden
#

Got it

ebon epoch
#

Back in 10min sorry

outer linden
#

No worries

ebon epoch
#

Sorry, 'im back

#

11 min, there was a lot of wind

ebon epoch
#

I'm using jetpackCompose so :
each time i had a create payment method screen, I had to rewrite paymentLauncher setup, rewrite configSetupIntentParams, rewrite handlePaymentResult.

Maybe i should try to wrap this kind of initial setup in a Component that i can reuse everywhere, with it's own viewModel.

#

Another was also on the fact that in the paymentmethodResult we don't have access to the paymentMethodId just created.
We're forced to refresh our whole payment method list from backend/Stripe

#

Our iOS Developers have access to it in their SDK

outer linden
#

Hold on

#

Can you point to exactly where you are creating the paymentmethod?

#

Oh wait, right. You only get the result that indicates whether the action completed or failed

ebon epoch
#

that's kinda a problem. But i've read the open code of the sdk and it's a choice from Stripe developers because in Stripe internal payment result, this is accessible

#

I need to perform some action to my backend after having created the payment and give the payment method ID to my backend but i cannot

outer linden
#

You are using a Setup Intent, correct?

ebon epoch
#

yes

outer linden
ebon epoch
#

and in a matter of responsability,
should I use the Stripe class directly from my viewModel ?
Or do you think i should see it as a data source and implement it as a repository ?

outer linden
#

Ultimately that is up to your architecture. I've only built direct Stripe integrations so I'm less familiar with your app's design.

#

If you are already mapping the data from the Stripe Card widget into your own data structure and passing that along, I think you could be able to abstract the Stripe-specific code behind a respository

#

But I haven't tried that specific approach

#

And honestly, I'm a pretty basic Android developer. I'm still not using Compose 😅

ebon epoch
# outer linden If you are already mapping the data from the Stripe Card widget into your own da...

Do you think this approach is compliant ? (on legal points, PCI)
because this what i've already done. (and i used a little trick with toParamsMap to get cvc, code etc from PaymentMethodParams.Cards )

Like with this approach i have:

A UI Component that is returning a Card Model.

Then I map the UI Model to a domain layer entity and access a CreatePaymentMethodUseCase.
In the usecase, i check my own business rules like checking if the users is not banned, suspended etc....

Then, i have port with an adapter.

This adapter call use the method in there https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe/index.html to confirm the setupIntent (that need to implement on my own the idempotent key, the payment exception result).

thids approach fit with my app archictecture.
My domain layer doesn't use any external depencies, his agnostics of Stripe.
If this PCi compliant, I should be good

ebon epoch
#

If this approach his too risky, my fallback can be:

Wrap the creation of Stripe card in a UI Component easily reusable, with its own viewModel.
One point that will not be fixed his that i must have a useCase in my domain representing the behaviour of create a setupIntent.

The only behaviour that should exist in my domain if the intent of creating a payment method.
But each approach have up and down ^^

outer linden
ebon epoch
#

I'll check that so

#

But in fact, I'm using Stripe SDK for collecting card infos, and using stripe SDK for sending it to the server

outer linden
#

Yeah but you are extracting the raw card PANs into your own data structure. I think that is okay because you don't store it outside the customer's phone but I cannot be 100% certain.

ebon epoch
#

Do you think I can find someone that can assert this is okey ?

outer linden
ebon epoch
#

Thanks, you helped me a lot !
I can teach more Compose Android if you want :p

#

Thanks for giving a part of your time and I'm sorry if sometimes my explanations were not clear

outer linden
#

I've got a book on it. I just haven't taken the time to work through it yet. I need to find the right personal project to build.

No worries about the explanations. It's a tough topic and I think you did a good job explaining what you were looking to do.

We're happy to help 🙂 It's why we are here.

ebon epoch
#

If you do otherwise, such as writing your own code to handle card information, you might be responsible for additional PCI DSS requirements (6.3-6.5) and would be ineligible for an SAQ A. Talk to a PCI QSA to determine how to best validate your compliance according to the current guidance from the PCI Council.

#

i found this in pci compliance

outer linden
#

Ah yes, that is where I was concerned

ebon epoch
#

I'll dig into it

outer linden
#

I recommend double checking with our Support team. They have specialists in many different areas and may have more insight into PCI compliance in moble applications

ebon epoch
#

Thanks a lot, good evening or have a nice day depending on your timezone x)