-
Notifications
You must be signed in to change notification settings - Fork 629
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LUXE next action support in PaymentSheet (#5318)
- Loading branch information
1 parent
abcc652
commit d5791b2
Showing
23 changed files
with
1,109 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
payments-core/src/main/java/com/stripe/android/model/LuxeActionCreatorForStatus.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.stripe.android.model | ||
|
||
import android.net.Uri | ||
import org.json.JSONObject | ||
|
||
internal data class LuxeActionCreatorForStatus( | ||
val status: StripeIntent.Status, | ||
val actionCreator: ActionCreator | ||
) { | ||
internal sealed class ActionCreator { | ||
fun create(stripeIntentJsonString: String) = | ||
create(JSONObject(stripeIntentJsonString)) | ||
|
||
internal abstract fun create(stripeIntentJson: JSONObject): LuxeNextActionRepository.Result | ||
internal data class RedirectActionCreator( | ||
val redirectPagePath: String, | ||
val returnToUrlPath: String | ||
) : ActionCreator() { | ||
override fun create(stripeIntentJson: JSONObject): LuxeNextActionRepository.Result { | ||
val returnUrl = getPath(returnToUrlPath, stripeIntentJson) | ||
val url = getPath(redirectPagePath, stripeIntentJson) | ||
return if ((returnUrl != null) && (url != null) | ||
) { | ||
LuxeNextActionRepository.Result.Action( | ||
StripeIntent.NextActionData.RedirectToUrl( | ||
returnUrl = returnUrl, | ||
url = Uri.parse(url) | ||
) | ||
) | ||
} else { | ||
LuxeNextActionRepository.Result.NotSupported | ||
} | ||
} | ||
} | ||
|
||
object NoActionCreator : ActionCreator() { | ||
override fun create(stripeIntentJson: JSONObject) = | ||
LuxeNextActionRepository.Result.NoAction | ||
} | ||
} | ||
|
||
internal companion object { | ||
/** | ||
* This function will take a path string like: next_action\[redirect]\[url] and | ||
* find that key path in the json object. | ||
*/ | ||
internal fun getPath(path: String?, json: JSONObject): String? { | ||
if (path == null) { | ||
return null | ||
} | ||
val pathArray = ("[*" + "([A-Za-z_0-9]+)" + "]*").toRegex().findAll(path) | ||
.map { it.value } | ||
.distinct() | ||
.filterNot { it.isEmpty() } | ||
.toList() | ||
var jsonObject: JSONObject? = json | ||
var pathIndex = 0 | ||
while (pathIndex < pathArray.size && | ||
jsonObject != null && | ||
jsonObject.opt(pathArray[pathIndex]) !is String | ||
) { | ||
val key = pathArray[pathIndex] | ||
if (jsonObject.has(key)) { | ||
val tempJsonObject = jsonObject.optJSONObject(key) | ||
|
||
if (tempJsonObject != null) { | ||
jsonObject = tempJsonObject | ||
} | ||
} | ||
pathIndex++ | ||
} | ||
return jsonObject?.opt(pathArray[pathArray.size - 1]) as? String | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
payments-core/src/main/java/com/stripe/android/model/LuxeNextActionRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package com.stripe.android.model | ||
|
||
import androidx.annotation.RestrictTo | ||
import androidx.annotation.VisibleForTesting | ||
import com.stripe.android.StripeIntentResult | ||
import org.json.JSONObject | ||
|
||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) | ||
class LuxeNextActionRepository { | ||
|
||
private val codeToNextActionSpec = mutableMapOf<String, LuxeAction>() | ||
|
||
internal fun update(additionalData: Map<String, LuxeAction>) { | ||
codeToNextActionSpec.putAll(additionalData) | ||
} | ||
|
||
@VisibleForTesting | ||
internal fun isPresent(code: PaymentMethodCode) = codeToNextActionSpec.contains(code) | ||
|
||
/** | ||
* Given the PaymentIntent retrieved after the returnUrl (not redirectUrl), based on | ||
* the Payment Method code and Status of the Intent what is the [StripeIntentResult.Outcome] | ||
* of the operation. | ||
*/ | ||
internal fun getPostAuthorizeIntentOutcome(stripeIntent: StripeIntent) = | ||
// This handles the case where the next action is not understood so | ||
// the PI is still in the requires action state. | ||
if (stripeIntent.requiresAction() && stripeIntent.nextActionData == null) { | ||
StripeIntentResult.Outcome.FAILED | ||
} else { | ||
codeToNextActionSpec[stripeIntent.paymentMethod?.code] | ||
?.postAuthorizeIntentStatus?.get(stripeIntent.status) | ||
} | ||
|
||
/** | ||
* Given the Intent returned from the confirm call, the payment method code and status | ||
* will be used to lookup the "instructions" for how to pull a next action from the | ||
* payment intent | ||
* | ||
* Return a [Result] that indicates if there is a next action, no next action, or | ||
* if it is not supported by the data in this repository. | ||
*/ | ||
internal fun getAction( | ||
lpmCode: PaymentMethodCode?, | ||
status: StripeIntent.Status?, | ||
stripeIntentJson: JSONObject | ||
) = getActionCreator(lpmCode, status) | ||
?.actionCreator?.create(stripeIntentJson) | ||
?: Result.NotSupported | ||
|
||
private fun getActionCreator(lpmCode: PaymentMethodCode?, status: StripeIntent.Status?) = | ||
codeToNextActionSpec[lpmCode]?.postConfirmStatusNextStatus.takeIf { it?.status == status } | ||
|
||
companion object { | ||
val Instance: LuxeNextActionRepository = LuxeNextActionRepository() | ||
} | ||
|
||
internal data class LuxeAction( | ||
/** | ||
* This should be null to use custom next action behavior coded in the SDK | ||
*/ | ||
val postConfirmStatusNextStatus: LuxeActionCreatorForStatus?, | ||
|
||
// Int here is @StripeIntentResult.Outcome | ||
val postAuthorizeIntentStatus: Map<StripeIntent.Status, Int> | ||
) | ||
|
||
internal sealed class Result { | ||
data class Action(val nextActionData: StripeIntent.NextActionData) : Result() | ||
object NoAction : Result() | ||
object NotSupported : Result() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.