Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Represent PaymentIntent confirmationMethod and captureMethod as enums #2559

Merged
merged 2 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
mshafrir-stripe marked this conversation as resolved.
Show resolved Hide resolved
/**
* (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
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")
}
}