Skip to content

Commit

Permalink
Represent PaymentIntent confirmationMethod and captureMethod as enums (
Browse files Browse the repository at this point in the history
  • Loading branch information
mshafrir-stripe authored Jun 9, 2020
1 parent ac5aab8 commit ddc0ef7
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 64 deletions.
31 changes: 31 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

## Migrating from versions < 15.0.0
- The SDK now targets JVM 1.8
- Changes to `PaymentIntent`
- `PaymentIntent#captureMethod` is now an enum, `PaymentIntent.CaptureMethod`
```kotlin
// before
if (paymentIntent.captureMethod == "automatic") {

} else if (paymentIntent.captureMethod == "manual") {

}

// after
when (paymentIntent.captureMethod) {
PaymentIntent.CaptureMethod.Automatic -> {}
PaymentIntent.CaptureMethod.Manual -> {}
}
```
- `PaymentIntent#confirmationMethod` is now an enum, `PaymentIntent.ConfirmationMethod`
```kotlin
// before
if (paymentIntent.confirmationMethod == "automatic") {

} else if (paymentIntent.confirmationMethod == "manual") {

}

// after
when (paymentIntent.confirmationMethod) {
PaymentIntent.ConfirmationMethod.Automatic -> {}
PaymentIntent.ConfirmationMethod.Manual -> {}
}
```
- Changes to `AddPaymentMethodActivity`
- When `CustomerSession` is instantiated with a `stripeAccountId`, it will be used in `AddPaymentMethodActivity`
when creating a payment method
Expand Down
69 changes: 51 additions & 18 deletions stripe/src/main/java/com/stripe/android/model/PaymentIntent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,10 @@ data class PaymentIntent internal constructor(
val cancellationReason: CancellationReason? = null,

/**
* One of `automatic` (default) or `manual`.
*
* When the capture method is `automatic`,
* Stripe automatically captures funds when the customer authorizes the payment.
* Controls when the funds will be captured from the customer’s account.
* See [CaptureMethod].
*/
val captureMethod: String?,
val captureMethod: CaptureMethod = CaptureMethod.Automatic,

/**
* The client secret of this [PaymentIntent]. Used for client-side retrieval using a
Expand All @@ -65,7 +63,7 @@ data class PaymentIntent internal constructor(
override val clientSecret: String?,

/**
* One of automatic (default) or manual.
* One of automatic (default) or manual. See [ConfirmationMethod].
*
* When [confirmationMethod] is `automatic`, a [PaymentIntent] can be confirmed
* using a publishable key. After `next_action`s are handled, no additional
Expand All @@ -77,7 +75,7 @@ data class PaymentIntent internal constructor(
* state after handling `next_action`s, and requires your server to initiate each
* payment attempt with an explicit confirmation.
*/
val confirmationMethod: String? = null,
val confirmationMethod: ConfirmationMethod = ConfirmationMethod.Automatic,

/**
* Time at which the object was created. Measured in seconds since the Unix epoch.
Expand Down Expand Up @@ -140,11 +138,11 @@ data class PaymentIntent internal constructor(
) : StripeIntent {
override val nextActionType: StripeIntent.NextActionType?
get() = when (nextActionData) {
is StripeIntent.NextActionData.SdkData -> StripeIntent.NextActionType.UseStripeSdk
is StripeIntent.NextActionData.RedirectToUrl -> StripeIntent.NextActionType.RedirectToUrl
is StripeIntent.NextActionData.DisplayOxxoDetails -> StripeIntent.NextActionType.DisplayOxxoDetails
else -> null
}
is StripeIntent.NextActionData.SdkData -> StripeIntent.NextActionType.UseStripeSdk
is StripeIntent.NextActionData.RedirectToUrl -> StripeIntent.NextActionType.RedirectToUrl
is StripeIntent.NextActionData.DisplayOxxoDetails -> StripeIntent.NextActionType.DisplayOxxoDetails
else -> null
}

override fun requiresAction(): Boolean {
return status === StripeIntent.Status.RequiresAction
Expand Down Expand Up @@ -217,9 +215,7 @@ data class PaymentIntent internal constructor(
RateLimitError("rate_limit_error");

internal companion object {
internal fun fromCode(typeCode: String?): Type? {
return values().firstOrNull { it.code == typeCode }
}
fun fromCode(typeCode: String?) = values().firstOrNull { it.code == typeCode }
}
}
}
Expand Down Expand Up @@ -300,9 +296,46 @@ data class PaymentIntent internal constructor(
Automatic("automatic");

internal companion object {
internal fun fromCode(code: String?): CancellationReason? {
return values().firstOrNull { it.code == code }
}
fun fromCode(code: String?) = values().firstOrNull { it.code == code }
}
}

/**
* Controls when the funds will be captured from the customer’s account.
*/
enum class CaptureMethod(private val code: String) {
/**
* (Default) Stripe automatically captures funds when the customer authorizes the payment.
*/
Automatic("automatic"),

/**
* Place a hold on the funds when the customer authorizes the payment, but don’t capture
* the funds until later. (Not all payment methods support this.)
*/
Manual("manual");

internal companion object {
fun fromCode(code: String?) = values().firstOrNull { it.code == code } ?: Automatic
}
}

enum class ConfirmationMethod(private val code: String) {
/**
* (Default) PaymentIntent can be confirmed using a publishable key. After `next_action`s
* are handled, no additional confirmation is required to complete the payment.
*/
Automatic("automatic"),

/**
* All payment attempts must be made using a secret key. The PaymentIntent returns to the
* `requires_confirmation` state after handling `next_action`s, and requires your server to
* initiate each payment attempt with an explicit confirmation.
*/
Manual("manual");

internal companion object {
fun fromCode(code: String?) = values().firstOrNull { it.code == code } ?: Automatic
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ internal class PaymentIntentJsonParser : ModelJsonParser<PaymentIntent> {
val cancellationReason = PaymentIntent.CancellationReason.fromCode(
optString(json, FIELD_CANCELLATION_REASON)
)
val captureMethod = optString(json, FIELD_CAPTURE_METHOD)
val captureMethod = PaymentIntent.CaptureMethod.fromCode(
optString(json, FIELD_CAPTURE_METHOD)
)
val clientSecret = optString(json, FIELD_CLIENT_SECRET)
val confirmationMethod = optString(json, FIELD_CONFIRMATION_METHOD)
val confirmationMethod = PaymentIntent.ConfirmationMethod.fromCode(
optString(json, FIELD_CONFIRMATION_METHOD)
)
val created = json.optLong(FIELD_CREATED)
val currency = StripeJsonUtils.optCurrency(json, FIELD_CURRENCY)
val description = optString(json, FIELD_DESCRIPTION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class PaymentIntentResultTest {
val paymentIntent = PaymentIntent(
created = 500L,
amount = 1000L,
captureMethod = "automatic",
clientSecret = "secret",
paymentMethod = PaymentMethodFixtures.BACS_DEBIT_PAYMENT_METHOD,
isLiveMode = false,
Expand Down
99 changes: 56 additions & 43 deletions stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ package com.stripe.android.model
import android.net.Uri
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

Expand All @@ -18,73 +14,92 @@ class PaymentIntentTest {
fun parseIdFromClientSecret_parsesCorrectly() {
val clientSecret = "pi_1CkiBMLENEVhOs7YMtUehLau_secret_s4O8SDh7s6spSmHDw1VaYPGZA"
val paymentIntentId = PaymentIntent.ClientSecret(clientSecret).paymentIntentId
assertEquals("pi_1CkiBMLENEVhOs7YMtUehLau", paymentIntentId)
assertThat(paymentIntentId)
.isEqualTo("pi_1CkiBMLENEVhOs7YMtUehLau")
}

@Test
fun parsePaymentIntentWithPaymentMethods() {
val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_MASTERCARD_3DS2
assertTrue(paymentIntent.requiresAction())
assertEquals("card", paymentIntent.paymentMethodTypes[0])
assertEquals(0, paymentIntent.canceledAt)
assertEquals("automatic", paymentIntent.captureMethod)
assertEquals("manual", paymentIntent.confirmationMethod)
assertNotNull(paymentIntent.nextAction)
assertEquals("[email protected]", paymentIntent.receiptEmail)
assertNull(paymentIntent.cancellationReason)
assertThat(paymentIntent.requiresAction())
.isTrue()
assertThat(paymentIntent.paymentMethodTypes)
.containsExactly("card")
assertThat(paymentIntent.canceledAt)
.isEqualTo(0)
assertThat(paymentIntent.captureMethod)
.isEqualTo(PaymentIntent.CaptureMethod.Automatic)
assertThat(paymentIntent.confirmationMethod)
.isEqualTo(PaymentIntent.ConfirmationMethod.Manual)
assertThat(paymentIntent.nextAction)
.isNotNull()
assertThat(paymentIntent.receiptEmail)
.isEqualTo("[email protected]")
assertThat(paymentIntent.cancellationReason)
.isNull()
}

@Test
fun getNextActionData_whenUseStripeSdkWith3ds2() {
val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_MASTERCARD_3DS2
assertTrue(paymentIntent.nextActionData is StripeIntent.NextActionData.SdkData.Use3DS2)
val sdkData = paymentIntent.nextActionData as StripeIntent.NextActionData.SdkData.Use3DS2
assertEquals("mastercard", sdkData.serverName)
assertThat(paymentIntent.nextActionData)
.isInstanceOf(StripeIntent.NextActionData.SdkData.Use3DS2::class.java)
val sdkData =
paymentIntent.nextActionData as StripeIntent.NextActionData.SdkData.Use3DS2
assertThat(sdkData.serverName)
.isEqualTo("mastercard")
}

@Test
fun getNextActionData_whenUseStripeSdkWith3ds1() {
val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_3DS1
assertTrue(paymentIntent.nextActionData is StripeIntent.NextActionData.SdkData.Use3DS1)
assertThat(paymentIntent.nextActionData)
.isInstanceOf(StripeIntent.NextActionData.SdkData.Use3DS1::class.java)
val sdkData = paymentIntent.nextActionData as StripeIntent.NextActionData.SdkData.Use3DS1
assertThat(sdkData.url).isNotEmpty()
assertThat(sdkData.url)
.isNotEmpty()
}

@Test
fun getNextActionData_whenRedirectToUrl() {
val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_REDIRECT
assertTrue(paymentIntent.nextActionData is StripeIntent.NextActionData.RedirectToUrl)
assertThat(paymentIntent.nextActionData)
.isInstanceOf(StripeIntent.NextActionData.RedirectToUrl::class.java)
val redirectData = paymentIntent.nextActionData as StripeIntent.NextActionData.RedirectToUrl
assertEquals(Uri.parse("https://hooks.stripe.com/3d_secure_2_eap/begin_test/src_1Ecaz6CRMbs6FrXfuYKBRSUG/src_client_secret_F6octeOshkgxT47dr0ZxSZiv"),
redirectData.url)
assertEquals(redirectData.returnUrl, "stripe://deeplink")
assertThat(redirectData.url)
.isEqualTo(
Uri.parse("https://hooks.stripe.com/3d_secure_2_eap/begin_test/src_1Ecaz6CRMbs6FrXfuYKBRSUG/src_client_secret_F6octeOshkgxT47dr0ZxSZiv")
)
assertThat(redirectData.returnUrl)
.isEqualTo("stripe://deeplink")
}

@Test
fun getLastPaymentError_parsesCorrectly() {
val lastPaymentError =
requireNotNull(PaymentIntentFixtures.PI_WITH_LAST_PAYMENT_ERROR.lastPaymentError)
assertEquals("pm_1F7J1bCRMbs6FrXfQKsYwO3U", lastPaymentError.paymentMethod?.id)
assertEquals("payment_intent_authentication_failure", lastPaymentError.code)
assertEquals(PaymentIntent.Error.Type.InvalidRequestError, lastPaymentError.type)
assertEquals(
"https://stripe.com/docs/error-codes/payment-intent-authentication-failure",
lastPaymentError.docUrl
)
assertEquals(
"The provided PaymentMethod has failed authentication. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again.",
lastPaymentError.message
)
assertThat(lastPaymentError.paymentMethod?.id)
.isEqualTo("pm_1F7J1bCRMbs6FrXfQKsYwO3U")
assertThat(lastPaymentError.code)
.isEqualTo("payment_intent_authentication_failure")
assertThat(lastPaymentError.type)
.isEqualTo(PaymentIntent.Error.Type.InvalidRequestError)
assertThat(lastPaymentError.docUrl)
.isEqualTo("https://stripe.com/docs/error-codes/payment-intent-authentication-failure")
assertThat(lastPaymentError.message)
.isEqualTo(
"The provided PaymentMethod has failed authentication. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again."
)
}

@Test
fun testCanceled() {
assertEquals(StripeIntent.Status.Canceled,
PaymentIntentFixtures.CANCELLED.status)
assertEquals(PaymentIntent.CancellationReason.Abandoned,
PaymentIntentFixtures.CANCELLED.cancellationReason)
assertEquals(1567091866L,
PaymentIntentFixtures.CANCELLED.canceledAt)
assertThat(PaymentIntentFixtures.CANCELLED.status)
.isEqualTo(StripeIntent.Status.Canceled)
assertThat(PaymentIntentFixtures.CANCELLED.cancellationReason)
.isEquivalentAccordingToCompareTo(PaymentIntent.CancellationReason.Abandoned)
assertThat(PaymentIntentFixtures.CANCELLED.canceledAt)
.isEqualTo(1567091866L)
}

@Test
Expand Down Expand Up @@ -112,9 +127,7 @@ class PaymentIntentTest {

@Test
fun clientSecret_withValidKeys_succeeds() {
assertEquals(
"pi_a1b2c3_secret_x7y8z9",
PaymentIntent.ClientSecret("pi_a1b2c3_secret_x7y8z9").value
)
assertThat(PaymentIntent.ClientSecret("pi_a1b2c3_secret_x7y8z9").value)
.isEqualTo("pi_a1b2c3_secret_x7y8z9")
}
}

0 comments on commit ddc0ef7

Please sign in to comment.