Skip to content

Commit

Permalink
Refactor Source class (#2569)
Browse files Browse the repository at this point in the history
Summary
- Rename `Source.SourceFlow` to `Source.Flow` and make it an enum
- Rename `Source.SourceStatus` to `Source.Status` and make it an enum
- Make `Source.Usage` an enum
- Move `SourceCodeVerification` to `Source.CodeVerification`
- Move `SourceOwner` to `Source.Owner`
- Move `SourceReceiver` to `Source.Receiver`
- Move `SourceRedirect` to `Source.Redirect`

Motivation
Make Source class more ergonomic

Testing
Updated tests
  • Loading branch information
mshafrir-stripe authored Jun 10, 2020
1 parent d931486 commit 97ec2ca
Show file tree
Hide file tree
Showing 25 changed files with 442 additions and 399 deletions.
8 changes: 8 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
- Changes to `AddPaymentMethodActivity`
- When `CustomerSession` is instantiated with a `stripeAccountId`, it will be used in `AddPaymentMethodActivity`
when creating a payment method
- Changes to `Source`
- `Source.SourceFlow` has been renamed to `Source.Flow` and is now an enum
- `Source.SourceStatus` has been renamed to `Source.Status` and is now an enum
- `Source.Usage` is now an enum
- `SourceCodeVerification` has been moved to `Source.CodeVerification`
- `SourceOwner` has been moved to `Source.Owner`
- `SourceReceiver` has been moved to `Source.Receiver`
- `SourceRedirect` has been moved to `Source.Redirect`
- Changes to `SourceTypeModel.Card`
- `SourceTypeModel.Card.ThreeDSecureStatus` is now an enum
- Changes to `BankAccount`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class CreateCardSourceActivity : AppCompatActivity() {
* Authenticate the [Source]
*/
private fun authenticateSource(source: Source) {
if (source.flow == Source.SourceFlow.REDIRECT) {
if (source.flow == Source.Flow.Redirect) {
createAuthenticateSourceDialog(source).let {
alertDialog = it
it.show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal class SourcesAdapter : RecyclerView.Adapter<SourcesAdapter.ViewHolder>(
private val viewBinding: SourcesListItemBinding
) : RecyclerView.ViewHolder(viewBinding.root) {
fun bind(source: Source) {
viewBinding.status.text = source.status
viewBinding.status.text = source.status?.toString()
viewBinding.redirectStatus.text = getRedirectStatus(source)
viewBinding.sourceId.text = source.id?.let { sourceId ->
sourceId.substring(sourceId.length - 6)
Expand All @@ -32,7 +32,7 @@ internal class SourcesAdapter : RecyclerView.Adapter<SourcesAdapter.ViewHolder>(
}

private fun getRedirectStatus(source: Source): String? {
return source.redirect?.status
return source.redirect?.status?.toString()
?: (source.sourceTypeModel as SourceTypeModel.Card).threeDSecureStatus.toString()
}
}
Expand Down
4 changes: 2 additions & 2 deletions stripe/src/main/java/com/stripe/android/Stripe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ class Stripe internal constructor(

/**
* Authenticate a [Source] that requires user action via a redirect (i.e. [Source.flow] is
* [Source.SourceFlow.REDIRECT].
* [Source.Flow.Redirect].
*
* The result of this operation will be returned via `Activity#onActivityResult(int, int, Intent)}}`
*
Expand All @@ -784,7 +784,7 @@ class Stripe internal constructor(

/**
* Authenticate a [Source] that requires user action via a redirect (i.e. [Source.flow] is
* [Source.SourceFlow.REDIRECT].
* [Source.Flow.Redirect].
*
* The result of this operation will be returned via `Activity#onActivityResult(int, int, Intent)}}`
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ internal class StripePaymentController internal constructor(
source: Source,
requestOptions: ApiRequest.Options
) {
if (source.flow == Source.SourceFlow.REDIRECT) {
if (source.flow == Source.Flow.Redirect) {
analyticsRequestExecutor.executeAsync(
analyticsRequestFactory.create(
analyticsDataFactory.createAuthSourceParams(
Expand Down
247 changes: 209 additions & 38 deletions stripe/src/main/java/com/stripe/android/model/Source.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.stripe.android.model

import androidx.annotation.StringDef
import com.stripe.android.model.Source.SourceFlow
import com.stripe.android.model.Source.Flow
import com.stripe.android.model.Source.SourceType
import com.stripe.android.model.parsers.SourceJsonParser
import kotlinx.android.parcel.Parcelize
Expand Down Expand Up @@ -37,7 +37,7 @@ data class Source internal constructor(
* Information related to the code verification flow. Present if the source is authenticated
* by a verification code (`flow` is `code_verification`).
*/
val codeVerification: SourceCodeVerification? = null,
val codeVerification: CodeVerification? = null,

/**
* Time at which the object was created. Measured in seconds since the Unix epoch.
Expand All @@ -55,8 +55,7 @@ data class Source internal constructor(
* The authentication `flow` of the source.
* `flow` is one of `redirect`, `receiver`, `code_verification`, `none`.
*/
@param:SourceFlow @field:SourceFlow @get:SourceFlow
val flow: String? = null,
val flow: Flow? = null,

/**
* Has the value true if the object exists in live mode or the value false if the object
Expand All @@ -74,26 +73,25 @@ data class Source internal constructor(
* Information about the owner of the payment instrument that may be used or required by
* particular source types.
*/
val owner: SourceOwner? = null,
val owner: Owner? = null,

/**
* Information related to the receiver flow.
* Present if the source is a receiver ([flow] is [SourceFlow.RECEIVER]).
* Present if the source is a receiver ([flow] is [Flow.Receiver]).
*/
val receiver: SourceReceiver? = null,
val receiver: Receiver? = null,

/**
* Information related to the redirect flow. Present if the source is authenticated by a
* redirect ([flow] is [SourceFlow.REDIRECT]).
* redirect ([flow] is [Flow.REDIRECT]).
*/
val redirect: SourceRedirect? = null,
val redirect: Redirect? = null,

/**
* The status of the source, one of `canceled`, `chargeable`, `consumed`, `failed`,
* or `pending`. Only `chargeable` sources can be used to create a charge.
*/
@param:SourceStatus @field:SourceStatus @get:SourceStatus
val status: String? = null,
val status: Status? = null,

val sourceTypeData: Map<String, @RawValue Any?>? = null,

Expand Down Expand Up @@ -121,8 +119,7 @@ data class Source internal constructor(
* types may or may not be reusable by construction, while others may leave the option at
* creation. If an incompatible value is passed, an error will be returned.
*/
@param:Usage @field:Usage @get:Usage
val usage: String? = null,
val usage: Usage? = null,

private val _weChat: WeChat? = null,

Expand Down Expand Up @@ -183,40 +180,214 @@ data class Source internal constructor(
}
}

@Retention(AnnotationRetention.SOURCE)
@StringDef(SourceStatus.PENDING, SourceStatus.CHARGEABLE, SourceStatus.CONSUMED,
SourceStatus.CANCELED, SourceStatus.FAILED)
annotation class SourceStatus {
companion object {
const val PENDING: String = "pending"
const val CHARGEABLE: String = "chargeable"
const val CONSUMED: String = "consumed"
const val CANCELED: String = "canceled"
const val FAILED: String = "failed"
/**
* The status of the source, one of `canceled`, `chargeable`, `consumed`, `failed`,
* or `pending`. Only `chargeable` sources can be used to create a charge.
*/
enum class Status(private val code: String) {
Canceled("canceled"),
Chargeable("chargeable"),
Consumed("consumed"),
Failed("failed"),
Pending("pending");

override fun toString(): String = code

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

@Retention(AnnotationRetention.SOURCE)
@StringDef(Usage.REUSABLE, Usage.SINGLE_USE)
annotation class Usage {
companion object {
const val REUSABLE: String = "reusable"
const val SINGLE_USE: String = "single_use"
/**
* Either `reusable` or `single_use`. Whether this source should be reusable or not.
* Some source types may or may not be reusable by construction, while others may leave the
* option at creation. If an incompatible value is passed, an error will be returned.
*/
enum class Usage(internal val code: String) {
Reusable("reusable"),
SingleUse("single_use");

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

@Retention(AnnotationRetention.SOURCE)
@StringDef(SourceFlow.REDIRECT, SourceFlow.RECEIVER, SourceFlow.CODE_VERIFICATION,
SourceFlow.NONE)
annotation class SourceFlow {
companion object {
const val REDIRECT: String = "redirect"
const val RECEIVER: String = "receiver"
const val CODE_VERIFICATION: String = "code_verification"
const val NONE: String = "none"
/**
* The authentication `flow` of the source.
*/
enum class Flow(internal val code: String) {
Redirect("redirect"),
Receiver("receiver"),
CodeVerification("code_verification"),
None("none");

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

/**
* Information related to the redirect flow. Present if the source is authenticated by a
* redirect ([flow] is [Flow.Redirect]).
*/
@Parcelize
data class Redirect(
/**
* The URL you provide to redirect the customer to after they authenticated their payment.
*/
val returnUrl: String?,

/**
* The status of the redirect, either
* `pending` (ready to be used by your customer to authenticate the transaction),
* `succeeded` (succesful authentication, cannot be reused) or
* `not_required` (redirect should not be used) or
* `failed` (failed authentication, cannot be reused).
*/
val status: Status?,

/**
* The URL provided to you to redirect a customer to as part of a `redirect`
* authentication flow.
*/
val url: String?
) : StripeModel {

enum class Status(private val code: String) {
Pending("pending"),
Succeeded("succeeded"),
NotRequired("not_required"),
Failed("failed");

override fun toString(): String = code

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

/**
* Information related to the code verification flow. Present if the source is authenticated
* by a verification code ([flow] is [Flow.CodeVerification]).
*/
@Parcelize
data class CodeVerification internal constructor(
/**
* The number of attempts remaining to authenticate the source object with a verification
* code.
*/
val attemptsRemaining: Int,

/**
* The status of the code verification, either
* `pending` (awaiting verification, `attempts_remaining` should be greater than 0),
* `succeeded` (successful verification) or
* `failed` (failed verification, cannot be verified anymore as `attempts_remaining` should be 0).
*/
val status: Status?
) : StripeModel {

enum class Status(private val code: String) {
Pending("pending"),
Succeeded("succeeded"),
Failed("failed");

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

/**
* Information related to the receiver flow. Present if [flow] is [Source.Flow.Receiver].
*/
@Parcelize
data class Receiver internal constructor(
/**
* The address of the receiver source. This is the value that should be communicated to the
* customer to send their funds to.
*/
val address: String?,

/**
* The total amount that was moved to your balance. This is almost always equal to the amount
* charged. In rare cases when customers deposit excess funds and we are unable to refund
* those, those funds get moved to your balance and show up in amount_charged as well.
* The amount charged is expressed in the source’s currency.
*/
val amountCharged: Long,

/**
* The total amount received by the receiver source.
* `amount_received = amount_returned + amount_charged` should be true for consumed sources
* unless customers deposit excess funds. The amount received is expressed in the source’s
* currency.
*/
val amountReceived: Long,

/**
* The total amount that was returned to the customer. The amount returned is expressed in
* the source’s currency.
*/
val amountReturned: Long
) : StripeModel

/**
* Information about the owner of the payment instrument that may be used or required by
* particular source types.
*/
@Parcelize
data class Owner internal constructor(
/**
* Owner’s address.
*/
val address: Address?,

/**
* Owner’s email address.
*/
val email: String?,

/**
* Owner’s full name.
*/
val name: String?,

/**
* Owner’s phone number (including extension).
*/
val phone: String?,

/**
* Verified owner’s address. Verified values are verified or provided by the payment
* method directly (and if supported) at the time of authorization or settlement.
* They cannot be set or mutated.
*/
val verifiedAddress: Address?,

/**
* Verified owner’s email address. Verified values are verified or provided by the
* payment method directly (and if supported) at the time of authorization or settlement.
* They cannot be set or mutated.
*/
val verifiedEmail: String?,

/**
* Verified owner’s full name. Verified values are verified or provided by the payment
* method directly (and if supported) at the time of authorization or settlement.
* They cannot be set or mutated.
*/
val verifiedName: String?,

/**
* Verified owner’s phone number (including extension). Verified values are verified or
* provided by the payment method directly (and if supported) at the time of authorization
* or settlement. They cannot be set or mutated.
*/
val verifiedPhone: String?
) : StripeModel

@Parcelize
data class Klarna(
val firstName: String?,
Expand Down
Loading

0 comments on commit 97ec2ca

Please sign in to comment.