Skip to content

Commit

Permalink
Add createPaymentDetails endpoint (#4941)
Browse files Browse the repository at this point in the history
  • Loading branch information
brnunes-stripe authored May 2, 2022
1 parent 66bea42 commit a2b6760
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 70 deletions.
23 changes: 23 additions & 0 deletions payments-core/api/payments-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,29 @@ public abstract class com/stripe/android/model/ConsumerPaymentDetails$PaymentDet
public fun isDefault ()Z
}

public abstract class com/stripe/android/model/ConsumerPaymentDetailsCreateParams : android/os/Parcelable, com/stripe/android/model/StripeParamsModel {
public static final field $stable I
public static final field Companion Lcom/stripe/android/model/ConsumerPaymentDetailsCreateParams$Companion;
public synthetic fun <init> (Lcom/stripe/android/model/PaymentMethod$Type;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun toParamMap ()Ljava/util/Map;
}

public final class com/stripe/android/model/ConsumerPaymentDetailsCreateParams$Card : com/stripe/android/model/ConsumerPaymentDetailsCreateParams {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public static final field Companion Lcom/stripe/android/model/ConsumerPaymentDetailsCreateParams$Card$Companion;
public fun <init> (Ljava/util/Map;)V
public fun describeContents ()I
public fun toParamMap ()Ljava/util/Map;
public fun writeToParcel (Landroid/os/Parcel;I)V
}

public final class com/stripe/android/model/ConsumerPaymentDetailsCreateParams$Card$Companion {
}

public final class com/stripe/android/model/ConsumerPaymentDetailsCreateParams$Companion {
}

public final class com/stripe/android/model/ConsumerSession$VerificationSession : com/stripe/android/core/model/StripeModel {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.stripe.android.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue

/**
* Model for Link Payment Method creation parameters, used for 'consumers/payment_details' endpoint.
*/
sealed class ConsumerPaymentDetailsCreateParams(
internal val type: PaymentMethod.Type
) : StripeParamsModel, Parcelable {

override fun toParamMap(): Map<String, Any> =
mapOf(PARAM_TYPE to type.code)

companion object {
private const val PARAM_TYPE = "type"
}

/**
* Represents a new Card payment method that will be created using the
* [cardPaymentMethodCreateParamsMap] values, converting from the [PaymentMethodCreateParams]
* format to [ConsumerPaymentDetailsCreateParams] format.
*/
@Parcelize
class Card(
private val cardPaymentMethodCreateParamsMap: Map<String, @RawValue Any>
) : ConsumerPaymentDetailsCreateParams(PaymentMethod.Type.Card) {
override fun toParamMap() = super.toParamMap()
.plus(convertParamsMap())

private fun convertParamsMap(): Map<String, Any> {
val params: MutableMap<String, Any> = mutableMapOf()
// card["billing_details"]["address"] becomes card["billing_address"]
(
(cardPaymentMethodCreateParamsMap[PARAM_BILLING_DETAILS] as? Map<*, *>)
?.get(PARAM_ADDRESS) as? Map<*, *>
)?.let {
params[PARAM_BILLING_ADDRESS] = mapOf(
// card["billing_details"]["address"]["country"]
// becomes card["billing_address"]["country_code"]
PARAM_COUNTRY_CODE to it[PARAM_COUNTRY],
PARAM_POSTAL_CODE to it[PARAM_POSTAL_CODE]
)
}
// only card number, exp_month and exp_year are included
(cardPaymentMethodCreateParamsMap[PARAM_CARD] as? Map<*, *>)?.let {
params[PARAM_CARD] = it.toMutableMap().filterKeys { key ->
key in setOf(PARAM_CARD_NUMBER, PARAM_CARD_EXP_MONTH, PARAM_CARD_EXP_YEAR)
}
}
return params
}

companion object {
private const val PARAM_CARD = "card"
private const val PARAM_CARD_NUMBER = "number"
private const val PARAM_CARD_EXP_MONTH = "exp_month"
private const val PARAM_CARD_EXP_YEAR = "exp_year"
private const val PARAM_BILLING_ADDRESS = "billing_address"
private const val PARAM_COUNTRY_CODE = "country_code"
private const val PARAM_POSTAL_CODE = "postal_code"

private const val PARAM_BILLING_DETAILS = "billing_details"
private const val PARAM_ADDRESS = "address"
private const val PARAM_COUNTRY = "country"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ data class PaymentMethodCreateParams internal constructor(
data class Link(
internal var paymentDetailsId: String,
internal var consumerSessionClientSecret: String,
internal var paymentMethodOptions: Map<String, Map<String, String>>? = null
internal var extraParams: Map<String, @RawValue Any>? = null
) : StripeParamsModel, Parcelable {
override fun toParamMap(): Map<String, Any> {
return mapOf(
Expand All @@ -465,9 +465,7 @@ data class PaymentMethodCreateParams internal constructor(
PARAM_CONSUMER_SESSION_CLIENT_SECRET to consumerSessionClientSecret
)
).plus(
paymentMethodOptions?.let {
mapOf(PARAM_PAYMENT_METHOD_OPTIONS to it)
} ?: emptyMap()
extraParams ?: emptyMap()
)
}

Expand All @@ -476,7 +474,6 @@ data class PaymentMethodCreateParams internal constructor(
private const val PARAM_CREDENTIALS = "credentials"
private const val PARAM_CONSUMER_SESSION_CLIENT_SECRET =
"consumer_session_client_secret"
private const val PARAM_PAYMENT_METHOD_OPTIONS = "payment_method_options"
}
}

Expand Down Expand Up @@ -854,11 +851,15 @@ data class PaymentMethodCreateParams internal constructor(
fun createLink(
paymentDetailsId: String,
consumerSessionClientSecret: String,
paymentMethodOptions: Map<String, Map<String, String>>? = null
extraParams: Map<String, @RawValue Any>? = null
): PaymentMethodCreateParams {
return PaymentMethodCreateParams(
type = PaymentMethod.Type.Link,
link = Link(paymentDetailsId, consumerSessionClientSecret, paymentMethodOptions)
link = Link(
paymentDetailsId,
consumerSessionClientSecret,
extraParams
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import org.json.JSONObject
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class ConsumerPaymentDetailsJsonParser : ModelJsonParser<ConsumerPaymentDetails> {
override fun parse(json: JSONObject): ConsumerPaymentDetails {
val paymentDetails = json.optJSONArray(FIELD_PAYMENT_DETAILS)
?.let { paymentDetailsArray ->
(0 until paymentDetailsArray.length())
.map { index -> paymentDetailsArray.getJSONObject(index) }
.mapNotNull { parsePaymentDetails(it) }
} ?: emptyList()
val paymentDetails = json.optJSONArray(FIELD_PAYMENT_DETAILS)?.let { paymentDetailsArray ->
(0 until paymentDetailsArray.length())
.map { index -> paymentDetailsArray.getJSONObject(index) }
.mapNotNull { parsePaymentDetails(it) }
} // Response with a single object might have it not in a list
?: json.optJSONObject(FIELD_PAYMENT_DETAILS)?.let {
parsePaymentDetails(it)
}?.let { listOf(it) } ?: emptyList()
return ConsumerPaymentDetails(paymentDetails)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_CLIENT_SECRET
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.CreateFinancialConnectionsSessionParams
Expand Down Expand Up @@ -1321,6 +1322,30 @@ internal class StripeApiRepository @JvmOverloads internal constructor(
}
}

override suspend fun createPaymentDetails(
consumerSessionClientSecret: String,
paymentDetailsCreateParams: ConsumerPaymentDetailsCreateParams,
requestOptions: ApiRequest.Options
): ConsumerPaymentDetails? {
return fetchStripeModel(
apiRequestFactory.createPost(
consumerPaymentDetailsUrl,
requestOptions,
mapOf(
"credentials" to mapOf(
"consumer_session_client_secret" to consumerSessionClientSecret
),
"active" to false
).plus(
paymentDetailsCreateParams.toParamMap()
)
),
ConsumerPaymentDetailsJsonParser()
) {
// no-op
}
}

/**
* Fetches the saved payment methods for the given customer.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.stripe.android.model.CardMetadata
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.CreateFinancialConnectionsSessionParams
Expand Down Expand Up @@ -434,13 +435,22 @@ abstract class StripeRepository {
requestOptions: ApiRequest.Options
): ConsumerSession?

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract suspend fun createPaymentDetails(
consumerSessionClientSecret: String,
paymentDetailsCreateParams: ConsumerPaymentDetailsCreateParams,
requestOptions: ApiRequest.Options
): ConsumerPaymentDetails?

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract suspend fun listPaymentDetails(
consumerSessionClientSecret: String,
paymentMethodTypes: Set<String>,
requestOptions: ApiRequest.Options
): ConsumerPaymentDetails?

// ACHv2 endpoints

internal abstract suspend fun createPaymentIntentFinancialConnectionsSession(
paymentIntentId: String,
params: CreateFinancialConnectionsSessionParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,44 @@ object ConsumerFixtures {
)
val CONSUMER_LOGGED_OUT = ConsumerSessionJsonParser().parse(CONSUMER_LOGGED_OUT_JSON)

val CONSUMER_SINGLE_PAYMENT_DETAILS_JSON = JSONObject(
"""
{
"redacted_payment_details": {
"id": "QAAAKJ6",
"bank_account_details": null,
"billing_address": {
"administrative_area": null,
"country_code": "US",
"dependent_locality": null,
"line_1": null,
"line_2": null,
"locality": null,
"name": null,
"postal_code": "12312",
"sorting_code": null
},
"billing_email_address": "",
"card_details": {
"brand": "MASTERCARD",
"checks": {
"address_line1_check": "STATE_INVALID",
"address_postal_code_check": "PASS",
"cvc_check": "PASS"
},
"exp_month": 12,
"exp_year": 2023,
"last4": "4444"
},
"is_default": true,
"type": "CARD"
}
}
""".trimIndent()
)
val CONSUMER_SINGLE_PAYMENT_DETAILS =
ConsumerPaymentDetailsJsonParser().parse(CONSUMER_SINGLE_PAYMENT_DETAILS_JSON)

val CONSUMER_PAYMENT_DETAILS_JSON = JSONObject(
"""
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.stripe.android.model

import com.google.common.truth.Truth.assertThat
import org.junit.Test

class ConsumerPaymentDetailsCreateParamsTest {

@Test
fun createCardParams_generatesCorrectParameters() {
assertThat(
ConsumerPaymentDetailsCreateParams.Card(
mapOf(
"ignored" to "none",
"card" to mapOf(
"number" to "123",
"cvc" to "321",
"brand" to "visa",
"exp_month" to "12",
"exp_year" to "2050"
),
"billing_details" to mapOf<String, Any>(
"address" to mapOf(
"country" to "US",
"postal_code" to "12345",
"extra" to "1"
)
)
)
).toParamMap()
).isEqualTo(
mapOf(
"type" to "card",
"card" to mapOf(
"number" to "123",
"exp_month" to "12",
"exp_year" to "2050"
),
"billing_address" to mapOf(
"country_code" to "US",
"postal_code" to "12345"
)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,36 @@ class PaymentMethodCreateParamsTest {
)
}

@Test
fun `createLink correctly set parameters`() {
val paymentDetailsId = "payment_details_123"
val consumerSessionClientSecret = "client_secret_123"
val extraParams = mapOf(
"card" to mapOf(
"cvc" to "123"
)
)

assertThat(
PaymentMethodCreateParams.createLink(
paymentDetailsId, consumerSessionClientSecret, extraParams
).toParamMap()
).isEqualTo(
mapOf(
"type" to "link",
"link" to mapOf(
"payment_details_id" to paymentDetailsId,
"credentials" to mapOf(
"consumer_session_client_secret" to consumerSessionClientSecret
),
"card" to mapOf(
"cvc" to "123"
)
)
)
)
}

private fun createFpx(): PaymentMethodCreateParams {
return PaymentMethodCreateParams.create(
PaymentMethodCreateParams.Fpx(bank = "hsbc"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,27 @@ import kotlin.test.assertEquals
class ConsumerPaymentDetailsJsonParserTest {

@Test
fun `parse card payment details`() {
fun `parse single card payment details`() {
assertEquals(
ConsumerPaymentDetailsJsonParser()
.parse(ConsumerFixtures.CONSUMER_SINGLE_PAYMENT_DETAILS_JSON),
ConsumerPaymentDetails(
listOf(
ConsumerPaymentDetails.Card(
id = "QAAAKJ6",
isDefault = true,
expiryYear = 2023,
expiryMonth = 12,
brand = CardBrand.MasterCard,
last4 = "4444"
)
)
)
)
}

@Test
fun `parse multiple card payment details`() {
assertEquals(
ConsumerPaymentDetailsJsonParser().parse(ConsumerFixtures.CONSUMER_PAYMENT_DETAILS_JSON),
ConsumerPaymentDetails(
Expand Down
Loading

0 comments on commit a2b6760

Please sign in to comment.