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

Fix Alipay integration #5554

Merged
merged 6 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Payments

* [CHANGED] [5552](https://github.com/stripe/stripe-android/pull/5552) Make `PaymentMethod.Card.networks` field public.
* [FIXED][5554](https://github.com/stripe/stripe-android/pull/5554) Fix Alipay integration when using the Alipay SDK.

## 20.12.0 - 2022-09-13
This release upgrades `compileSdkVersion` to 33, updates Google Pay button to match the new brand
Expand Down
1 change: 1 addition & 0 deletions example/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<activity android:name=".activity.ComposeExampleActivity" />
<activity android:name=".activity.KlarnaPaymentActivity"/>
<activity android:name=".activity.AffirmPaymentActivity"/>
<activity android:name=".activity.AlipayPaymentActivity"/>
<activity android:name=".activity.ConnectUSBankAccountActivity"/>
<activity android:name=".activity.ManualUSBankAccountPaymentMethodActivity"/>
</application>
Expand Down
3 changes: 3 additions & 0 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def getAccountId() {
dependencies {
implementation project(':payments')
implementation project(':financial-connections')

implementation("com.alipay.sdk:alipaysdk-android:15.8.11")

implementation "androidx.appcompat:appcompat:$androidxAppcompatVersion"
implementation "androidx.recyclerview:recyclerview:$androidxRecyclerviewVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidxLifecycleVersion"
Expand Down
4 changes: 4 additions & 0 deletions example/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
<string name="confirm_affirm_button">Confirm with Affirm</string>
<string name="confirm_with_affirm">Affirm Payment Intent Example</string>

<string name="alipay_example_intro">Tapping the button below will create a PaymentIntent and then use Alipay to confirm it</string>
<string name="confirm_alipay_button">Confirm with Alipay</string>
<string name="confirm_with_alipay">Alipay Payment Intent Example</string>

<!-- SEPA Debit -->
<string name="sepa_debit_mandate">By providing your IBAN and confirming this payment, you are authorizing EXAMPLE COMPANY NAME and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.</string>
<string name="iban_hint">IBAN</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.stripe.example.activity

import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import com.alipay.sdk.app.PayTask
import com.stripe.android.ApiResultCallback
import com.stripe.android.PaymentConfiguration
import com.stripe.android.PaymentIntentResult
import com.stripe.android.Stripe
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.MandateDataParams
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.StripeIntent
import com.stripe.example.R
import com.stripe.example.databinding.PaymentExampleActivityBinding
import org.json.JSONObject

class AlipayPaymentActivity : StripeIntentActivity() {

private val viewBinding: PaymentExampleActivityBinding by lazy {
PaymentExampleActivityBinding.inflate(layoutInflater)
}

private val stripe: Stripe by lazy {
Stripe(
applicationContext,
PaymentConfiguration.getInstance(applicationContext).publishableKey
)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)

viewBinding.confirmWithPaymentButton.text =
resources.getString(R.string.confirm_alipay_button)
viewBinding.paymentExampleIntro.text =
resources.getString(R.string.alipay_example_intro)

viewModel.inProgress.observe(this) { enableUi(!it) }
viewModel.status.observe(this, Observer(viewBinding.status::setText))

viewBinding.confirmWithPaymentButton.setOnClickListener {
createAndConfirmPaymentIntent(
country = "US",
paymentMethodCreateParams = PaymentMethodCreateParams.createAlipay(),
supportedPaymentMethods = "alipay"
)
}
}

override fun handleCreatePaymentIntentResponse(
responseData: JSONObject,
params: PaymentMethodCreateParams?,
shippingDetails: ConfirmPaymentIntentParams.Shipping?,
stripeAccountId: String?,
existingPaymentMethodId: String?,
mandateDataParams: MandateDataParams?,
onPaymentIntentCreated: (String) -> Unit
) {
val secret = responseData.getString("secret")
onPaymentIntentCreated(secret)
viewModel.status.value +=
"\n\nStarting PaymentIntent confirmation" +
(
stripeAccountId?.let {
" for $it"
} ?: ""
)

stripe.confirmAlipayPayment(
confirmPaymentIntentParams = ConfirmPaymentIntentParams.createAlipay(secret),
authenticator = { data ->
PayTask(this).payV2(data, true)
},
callback = object : ApiResultCallback<PaymentIntentResult> {
override fun onSuccess(result: PaymentIntentResult) {
val paymentIntent = result.intent
when (paymentIntent.status) {
StripeIntent.Status.Succeeded ->
updateStatus("\n\nPayment succeeded")
StripeIntent.Status.RequiresAction ->
stripe.handleNextActionForPayment(this@AlipayPaymentActivity, secret)
else -> updateStatus("\n\nPayment failed or canceled")
}
}

override fun onError(e: Exception) {
updateStatus("\n\nError: ${e.message}")
}
}
)
}

private fun updateStatus(appendMessage: String) {
viewModel.status.value += appendMessage
viewModel.inProgress.postValue(false)
}

private fun enableUi(enable: Boolean) {
viewBinding.progressBar.visibility = if (enable) View.INVISIBLE else View.VISIBLE
viewBinding.confirmWithPaymentButton.isEnabled = enable
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ class LauncherActivity : AppCompatActivity() {
activity.getString(R.string.confirm_with_affirm),
AffirmPaymentActivity::class.java
),
Item(
activity.getString(R.string.confirm_with_alipay),
AlipayPaymentActivity::class.java
),
Item(
activity.getString(R.string.becs_debit_example),
BecsDebitPaymentMethodActivity::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ abstract class StripeIntentActivity : AppCompatActivity() {
}
}

private fun handleCreatePaymentIntentResponse(
open fun handleCreatePaymentIntentResponse(
responseData: JSONObject,
params: PaymentMethodCreateParams?,
shippingDetails: ConfirmPaymentIntentParams.Shipping?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ internal abstract class IdentityDocumentScanFragment(
notSubmitBlock =
if (verificationPage.requireSelfie()) {
({ findNavController().navigate(R.id.action_global_selfieFragment) })
} else null
} else {
null
}
)
}.onFailure { throwable ->
Log.e(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ import com.stripe.android.payments.core.injection.PRODUCT_USAGE
import com.stripe.android.utils.StripeUrlUtils
import kotlinx.coroutines.Dispatchers
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.HttpURLConnection
import java.security.Security
Expand Down Expand Up @@ -1112,7 +1111,7 @@ class StripeApiRepository @JvmOverloads internal constructor(
override suspend fun retrieveObject(
url: String,
requestOptions: ApiRequest.Options
): JSONObject {
): StripeResponse<String> {
if (!StripeUrlUtils.isStripeUrl(url)) {
throw IllegalArgumentException("Unrecognized domain: $url")
}
Expand All @@ -1125,7 +1124,7 @@ class StripeApiRepository @JvmOverloads internal constructor(
fireAnalyticsRequest(PaymentAnalyticsEvent.StripeUrlRetrieve)
}

return response.responseJson()
return response
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.stripe.android.core.exception.InvalidRequestException
import com.stripe.android.core.model.StripeFile
import com.stripe.android.core.model.StripeFileParams
import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.core.networking.StripeResponse
import com.stripe.android.exception.CardException
import com.stripe.android.model.BankStatuses
import com.stripe.android.model.CardMetadata
Expand Down Expand Up @@ -40,7 +41,6 @@ import com.stripe.android.model.StripeIntent
import com.stripe.android.model.Token
import com.stripe.android.model.TokenParams
import org.json.JSONException
import org.json.JSONObject
import java.util.Locale

/**
Expand Down Expand Up @@ -391,7 +391,7 @@ abstract class StripeRepository {
internal abstract suspend fun retrieveObject(
url: String,
requestOptions: ApiRequest.Options
): JSONObject
): StripeResponse<String>

internal abstract suspend fun createRadarSession(
requestOptions: ApiRequest.Options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.stripe.android.core.exception.APIException
import com.stripe.android.core.model.StripeFile
import com.stripe.android.core.model.StripeFileParams
import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.core.networking.StripeResponse
import com.stripe.android.model.BankStatuses
import com.stripe.android.model.BinFixtures
import com.stripe.android.model.CardMetadata
Expand Down Expand Up @@ -34,7 +35,6 @@ import com.stripe.android.model.Stripe3ds2AuthResultFixtures
import com.stripe.android.model.StripeIntent
import com.stripe.android.model.Token
import com.stripe.android.model.TokenParams
import org.json.JSONObject
import java.util.Locale

internal abstract class AbsFakeStripeRepository : StripeRepository() {
Expand Down Expand Up @@ -279,7 +279,7 @@ internal abstract class AbsFakeStripeRepository : StripeRepository() {
override suspend fun retrieveObject(
url: String,
requestOptions: ApiRequest.Options
) = JSONObject()
) = StripeResponse(1, "response")

override suspend fun createRadarSession(
requestOptions: ApiRequest.Options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,28 @@ internal class StripeApiRepositoryTest {
verifyAnalyticsRequest(PaymentAnalyticsEvent.FileCreate)
}

@Test
fun retrieveObject_shouldFireExpectedRequestsAndNotParseResult() = runTest {
val responseBody = "not a valid json"
whenever(stripeNetworkClient.executeRequest(any<ApiRequest>()))
.thenReturn(
StripeResponse(
200,
responseBody,
emptyMap()
)
)

val response = create().retrieveObject(
StripeApiRepository.paymentMethodsUrl,
DEFAULT_OPTIONS
)

verify(stripeNetworkClient).executeRequest(any())
assertThat(response.body).isEqualTo(responseBody)
verifyAnalyticsRequest(PaymentAnalyticsEvent.StripeUrlRetrieve)
}

@Test
fun apiRequest_withErrorResponse_onUnsupportedSdkVersion_shouldNotBeTranslated() =
runTest {
Expand Down