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

Add klarna to sdk w/ example #4325

Merged
merged 2 commits into from
Oct 27, 2021
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
1 change: 1 addition & 0 deletions example/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<activity android:name=".activity.SimpleConfirmationActivity" />
<activity android:name=".activity.ConnectExampleActivity" />
<activity android:name=".activity.ComposeExampleActivity" />
<activity android:name=".activity.KlarnaPaymentActivity"/>
</application>

</manifest>
43 changes: 43 additions & 0 deletions example/res/layout/klarna_payment_activity.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingBottom="20dp">

<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
style="@style/Widget.MaterialComponents.LinearProgressIndicator" />

<TextView
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/klarna_example_intro" />

<Button
android:id="@+id/confirm_with_klarna_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="@string/confirm_klarna_button" />

<TextView
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:typeface="monospace"
android:layout_marginTop="40dp"/>
</LinearLayout>
</ScrollView>
4 changes: 4 additions & 0 deletions example/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
<string name="create_klarna_source">Create and Authenticate Klarna Source</string>
<string name="fetch_klarna_source">Fetch Klarna Source</string>

<string name="klarna_example_intro">Tapping the button below will create a PaymentIntent and then use Klarna to confirm it</string>
<string name="confirm_klarna_button">Confirm with Klarna</string>
<string name="confirm_with_klarna">Klarna 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,44 @@
package com.stripe.example.activity

import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.example.databinding.KlarnaPaymentActivityBinding

class KlarnaPaymentActivity : StripeIntentActivity() {

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

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

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

viewBinding.confirmWithKlarnaButton.setOnClickListener {
createAndConfirmPaymentIntent("US", confirmParams, "klarna")
}
}

private fun enableUi(enable: Boolean) {
viewBinding.progressBar.visibility = if (enable) View.INVISIBLE else View.VISIBLE
viewBinding.confirmWithKlarnaButton.isEnabled = enable
}

private companion object {
private val confirmParams = PaymentMethodCreateParams.createKlarna(
billingDetails = PaymentMethod.BillingDetails(
email = "[email protected]",
address = Address.Builder()
.setCountry("US")
.build()
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class LauncherActivity : AppCompatActivity() {
activity.getString(R.string.klarna_source_example),
KlarnaSourceActivity::class.java
),
Item(
activity.getString(R.string.confirm_with_klarna),
KlarnaPaymentActivity::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 @@ -74,6 +74,7 @@ abstract class StripeIntentActivity : AppCompatActivity() {
protected fun createAndConfirmPaymentIntent(
country: String,
paymentMethodCreateParams: PaymentMethodCreateParams?,
supportedPaymentMethods: String? = null,
shippingDetails: ConfirmPaymentIntentParams.Shipping? = null,
stripeAccountId: String? = null,
existingPaymentMethodId: String? = null,
Expand All @@ -84,7 +85,10 @@ abstract class StripeIntentActivity : AppCompatActivity() {

keyboardController.hide()

viewModel.createPaymentIntent(country).observe(
viewModel.createPaymentIntent(
country = country,
supportedPaymentMethods = supportedPaymentMethods
).observe(
this,
{ result ->
result.onSuccess {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ internal class StripeIntentViewModel(

fun createPaymentIntent(
country: String,
customerId: String? = null
customerId: String? = null,
supportedPaymentMethods: String? = null
) = makeBackendRequest(
R.string.creating_payment_intent,
R.string.payment_intent_status
Expand All @@ -33,6 +34,10 @@ internal class StripeIntentViewModel(
customerId?.let {
mapOf("customer_id" to it)
}.orEmpty()
).plus(
supportedPaymentMethods?.let {
mapOf("supported_payment_methods" to it)
Copy link
Contributor

@ccen-stripe ccen-stripe Oct 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder should we exhaust this to customize all possible params provided by the toy server(not needed in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. It might be good to just have them all there in case we need them in the future. Unfortunately, it's not super well documented. That's why I only added this one for now.

}.orEmpty()
)
.toMutableMap()
)
Expand Down
8 changes: 8 additions & 0 deletions payments-core/api/payments-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -3085,6 +3085,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum,
public static final field Giropay Lcom/stripe/android/model/PaymentMethod$Type;
public static final field GrabPay Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Ideal Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Klarna Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Netbanking Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Oxxo Lcom/stripe/android/model/PaymentMethod$Type;
public static final field P24 Lcom/stripe/android/model/PaymentMethod$Type;
Expand Down Expand Up @@ -3189,6 +3190,9 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/
public static final fun createGiropay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createGrabPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createGrabPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createKlarna ()Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createKlarna (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createKlarna (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createOxxo (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createOxxo (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static final fun createP24 (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
Expand Down Expand Up @@ -3343,6 +3347,10 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Companion
public final fun createGrabPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createGrabPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static synthetic fun createGrabPay$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createKlarna ()Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createKlarna (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createKlarna (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static synthetic fun createKlarna$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createOxxo (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun createOxxo (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams;
public static synthetic fun createOxxo$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,13 @@ constructor(
isVoucher = false,
requiresMandate = false,
hasDelayedSettlement = false
),
Klarna(
"klarna",
isReusable = false,
isVoucher = false,
requiresMandate = false,
hasDelayedSettlement = false
);

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For paymentsheet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,19 @@ data class PaymentMethodCreateParams internal constructor(
)
}

@JvmStatic
@JvmOverloads
fun createKlarna(
billingDetails: PaymentMethod.BillingDetails? = null,
metadata: Map<String, String>? = null
): PaymentMethodCreateParams {
return PaymentMethodCreateParams(
type = PaymentMethod.Type.Klarna,
billingDetails = billingDetails,
metadata = metadata
)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For paymentsheet
fun createWithOverride(
type: PaymentMethod.Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
skyler-stripe marked this conversation as resolved.
Show resolved Hide resolved
import com.google.common.truth.Truth.assertThat
import com.stripe.android.exception.InvalidRequestException
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodCreateParamsFixtures
Expand Down Expand Up @@ -338,4 +339,47 @@ internal class PaymentMethodEndToEndTest {
assertThat(paymentMethod?.type)
.isEqualTo(PaymentMethod.Type.WeChatPay)
}

@Test
fun createPaymentMethod_withKlarna_shouldCreateObject() {
val missingAddressException = assertFailsWith<InvalidRequestException>(
"Address is required to create a klarna payment method"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are these messages from? is it possible to build them programmatically?

Copy link
Contributor Author

@skyler-stripe skyler-stripe Oct 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These messages come from the Stripe api's error messages, we parse the response. So there's not really any way to generate them programatically. I was following the after pay example here. I don't think there's a much cleaner way.

) {
val params = PaymentMethodCreateParams.createKlarna(
billingDetails =
PaymentMethodCreateParamsFixtures.BILLING_DETAILS.copy(address = null)
)
Stripe(context, ApiKeyFixtures.KLARNA_PUBLISHABLE_KEY)
.createPaymentMethodSynchronous(params)
}

assertThat(missingAddressException.message)
.isEqualTo("You must provide `billing_details[address][country]` to use Klarna.")

val missingCountryException = assertFailsWith<InvalidRequestException>(
"Country is required to create a klarna payment method"
) {
val address = Address(country = null)
val params = PaymentMethodCreateParams.createKlarna(
billingDetails =
PaymentMethodCreateParamsFixtures.BILLING_DETAILS.copy(address = address)
)

Stripe(context, ApiKeyFixtures.KLARNA_PUBLISHABLE_KEY)
.createPaymentMethodSynchronous(params)
}

assertThat(missingCountryException.message)
.isEqualTo("You must provide `billing_details[address][country]` to use Klarna.")

val params = PaymentMethodCreateParams.createKlarna(
billingDetails = PaymentMethodCreateParamsFixtures.BILLING_DETAILS.copy()
)

val paymentMethod =
Stripe(context, ApiKeyFixtures.KLARNA_PUBLISHABLE_KEY)
.createPaymentMethodSynchronous(params)

assertThat(paymentMethod?.type).isEqualTo(PaymentMethod.Type.Klarna)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1179,4 +1179,65 @@ internal object PaymentIntentFixtures {

val PI_REFRESH_RESPONSE_WECHAT_PAY_SUCCESS =
PARSER.parse(PI_REFRESH_RESPONSE_WECHAT_PAY_SUCCESS_JSON)!!

val PI_WITH_KLARNA_IN_PAYMENT_METHODS_JSON = JSONObject(
"""
{
"id": "pi_3JoznDLu5o3P18Zp0lRzng7p",
"object": "payment_intent",
"amount": 1099,
"amount_capturable": 0,
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [

],
"has_more": false,
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_3JoznDLu5o3P18Zp0lRzng7p"
},
"client_secret": "pi_3JoznDLu5o3P18Zp0lRzng7p_secret_2wQkxNRVYQOzBcG6XGbw2tyBv",
"confirmation_method": "automatic",
"created": 1635293699,
"currency": "eur",
"customer": null,
"description": null,
"invoice": null,
"last_payment_error": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"klarna": {
"preferred_locale": null
}
},
"payment_method_types": [
"klarna"
],
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
""".trimIndent()
)

val PI_WITH_KLARNA_IN_PAYMENT_METHODS =
PARSER.parse(PI_WITH_KLARNA_IN_PAYMENT_METHODS_JSON)!!
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ class PaymentIntentTest {
.containsExactly("wechat_pay")
}

@Test
fun parsePaymentIntentWithKlarnaPaymentMethods() {
val paymentIntent = PaymentIntentFixtures.PI_WITH_KLARNA_IN_PAYMENT_METHODS
assertThat(paymentIntent.paymentMethodTypes)
.containsExactly("klarna")
}

@Test
fun getNextActionData_whenUseStripeSdkWith3ds2() {
val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_MASTERCARD_3DS2
Expand Down