Skip to content

Commit

Permalink
Persist PaymentConfiguration to SharedPreferences (#1479)
Browse files Browse the repository at this point in the history
This change allows the `PaymentConfiguration` instance
to be restored on app/activity death.

This is a minor breaking change, because it requires the
user to pass in a `Context`.
  • Loading branch information
mshafrir-stripe authored Sep 5, 2019
1 parent 3453be7 commit 777e2e8
Show file tree
Hide file tree
Showing 24 changed files with 143 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CardTokenActivity : AppCompatActivity() {
this,
findViewById(R.id.card_input_widget),
findViewById(R.id.listview),
PaymentConfiguration.getInstance().publishableKey
PaymentConfiguration.getInstance(this).publishableKey
)

dependencyHandler.attachAsyncTaskTokenController(findViewById(R.id.save))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ class FragmentExamplesActivity : AppCompatActivity() {
super.onActivityCreated(savedInstanceState)

backendApi = RetrofitFactory.instance.create(BackendApi::class.java)
stripe = Stripe(requireContext(), PaymentConfiguration.getInstance().publishableKey)
stripe = Stripe(
requireContext(),
PaymentConfiguration.getInstance(requireContext()).publishableKey
)
paymentSession = createPaymentSession(createCustomerSession())

val rootView = view!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class LauncherActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launcher)

PaymentConfiguration.init(Settings.PUBLISHABLE_KEY)
PaymentConfiguration.init(this, Settings.PUBLISHABLE_KEY)

val examples = findViewById<RecyclerView>(R.id.examples)
val linearLayoutManager = LinearLayoutManager(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class PayWithGoogleActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pay_with_google)

stripe = Stripe(this, PaymentConfiguration.getInstance().publishableKey)
stripe = Stripe(this, PaymentConfiguration.getInstance(this).publishableKey)

paymentsClient = Wallet.getPaymentsClient(this,
Wallet.WalletOptions.Builder()
Expand Down Expand Up @@ -195,7 +195,8 @@ class PayWithGoogleActivity : AppCompatActivity() {
.put(
"parameters", cardPaymentMethodParams
)
.put("tokenizationSpecification", GooglePayConfig().tokenizationSpecification)
.put("tokenizationSpecification",
GooglePayConfig(this).tokenizationSpecification)

val paymentDataRequest = JSONObject()
.put("apiVersion", 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ class PaymentAuthActivity : AppCompatActivity() {
}

backendApi = RetrofitFactory.instance.create(BackendApi::class.java)
val publishableKey = PaymentConfiguration.getInstance(this).publishableKey
stripe = if (stripeAccountId != null) {
Stripe(this, PaymentConfiguration.getInstance().publishableKey, stripeAccountId)
Stripe(this, publishableKey, stripeAccountId)
} else {
Stripe(this, PaymentConfiguration.getInstance().publishableKey)
Stripe(this, publishableKey)
}

buyButton = findViewById(R.id.buy_button)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class PaymentIntentActivity : AppCompatActivity() {

progressDialogController = ProgressDialogController(supportFragmentManager, resources)
errorDialogHandler = ErrorDialogHandler(this)
stripe = Stripe(applicationContext, PaymentConfiguration.getInstance().publishableKey)
stripe = Stripe(applicationContext, PaymentConfiguration.getInstance(this).publishableKey)
val retrofit = RetrofitFactory.instance
backendApi = retrofit.create(BackendApi::class.java)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ class PaymentMultilineActivity : AppCompatActivity() {
private fun saveCard() {
val card = cardMultilineWidget.card ?: return

val stripe = Stripe(applicationContext,
PaymentConfiguration.getInstance().publishableKey)
val stripe = Stripe(
applicationContext,
PaymentConfiguration.getInstance(applicationContext).publishableKey
)
val cardSourceParams = PaymentMethodCreateParams.create(card.toPaymentMethodParamsCard(), null)
// Note: using this style of Observable creation results in us having a method that
// will not be called until we subscribe to it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class RedirectActivity : AppCompatActivity() {
resources)
redirectDialogController = RedirectDialogController(this)

val publishableKey = PaymentConfiguration.getInstance().publishableKey
val publishableKey = PaymentConfiguration.getInstance(this).publishableKey
stripe = Stripe(applicationContext, publishableKey)

val threeDSecureButton = findViewById<Button>(R.id.btn_three_d_secure)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SetupIntentActivity : AppCompatActivity() {
errorDialogHandler = ErrorDialogHandler(this)

stripe = Stripe(this,
PaymentConfiguration.getInstance().publishableKey)
PaymentConfiguration.getInstance(this).publishableKey)
val retrofit = RetrofitFactory.instance
backendApi = retrofit.create(BackendApi::class.java)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class TokenIntentService : IntentService("TokenIntentService") {
val card = Card.create(cardNumber, month, year, cvc)

val stripe = Stripe(applicationContext,
PaymentConfiguration.getInstance().publishableKey)
PaymentConfiguration.getInstance(this).publishableKey)
try {
token = stripe.createTokenSynchronous(card)
} catch (stripeEx: StripeException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ class PaymentActivity : AppCompatActivity() {
.build())
.build())

val publishableKey = PaymentConfiguration.getInstance(this).publishableKey
stripe = if (Settings.STRIPE_ACCOUNT_ID != null) {
Stripe(this, PaymentConfiguration.getInstance().publishableKey,
Settings.STRIPE_ACCOUNT_ID)
Stripe(this, publishableKey, Settings.STRIPE_ACCOUNT_ID)
} else {
Stripe(this, PaymentConfiguration.getInstance().publishableKey)
Stripe(this, publishableKey)
}

service = RetrofitFactory.instance.create(BackendApi::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class StoreActivity : AppCompatActivity(), StoreAdapter.TotalItemsChangedListene
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_store)

PaymentConfiguration.init(Settings.PUBLISHABLE_KEY)
PaymentConfiguration.init(this, Settings.PUBLISHABLE_KEY)
goToCartButton = findViewById(R.id.fab_checkout)
storeAdapter = StoreAdapter(this, priceMultiplier)

Expand Down
4 changes: 2 additions & 2 deletions stripe/src/main/java/com/stripe/android/CustomerSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public class CustomerSession {
/**
* Create a CustomerSession with the provided {@link EphemeralKeyProvider}.
*
* <p>You must call {@link PaymentConfiguration#init(String)} with your publishable key
* <p>You must call {@link PaymentConfiguration#init(Context, String)} with your publishable key
* before calling this method.</p>
*
* @param context The application context
Expand All @@ -141,7 +141,7 @@ public static void initCustomerSession(@NonNull Context context,
@Nullable String stripeAccountId,
boolean shouldPrefetchEphemeralKey) {
setInstance(new CustomerSession(context, ephemeralKeyProvider, Stripe.getAppInfo(),
PaymentConfiguration.getInstance().getPublishableKey(),
PaymentConfiguration.getInstance(context).getPublishableKey(),
stripeAccountId, shouldPrefetchEphemeralKey));
}

Expand Down
5 changes: 3 additions & 2 deletions stripe/src/main/java/com/stripe/android/GooglePayConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android;

import android.content.Context;
import android.support.annotation.NonNull;

import org.json.JSONException;
Expand All @@ -14,8 +15,8 @@ public final class GooglePayConfig {
* Instantiate with {@link PaymentConfiguration}. {@link PaymentConfiguration} must be
* initialized.
*/
public GooglePayConfig() {
this(PaymentConfiguration.getInstance().getPublishableKey());
public GooglePayConfig(@NonNull Context context) {
this(PaymentConfiguration.getInstance(context).getPublishableKey());
}

public GooglePayConfig(@NonNull String publishableKey) {
Expand Down
51 changes: 47 additions & 4 deletions stripe/src/main/java/com/stripe/android/PaymentConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.stripe.android;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
Expand Down Expand Up @@ -36,11 +38,22 @@ public PaymentConfiguration[] newArray(int size) {
}
};

/**
* Attempts to load a {@link PaymentConfiguration} instance. First attempt to use the class's
* singleton instance. If unavailable, attempt to load from {@link Store}.
*
* @param context application context
* @return a {@link PaymentConfiguration} instance, or throw an exception
*/
@NonNull
public static PaymentConfiguration getInstance() {
public static PaymentConfiguration getInstance(@NonNull Context context) {
if (mInstance == null) {
throw new IllegalStateException(
"Attempted to get instance of PaymentConfiguration without initialization.");
final PaymentConfiguration loadedInstance = new Store(context).load();
if (loadedInstance != null) {
mInstance = loadedInstance;
} else {
throw new IllegalStateException("PaymentConfiguration was not initialized");
}
}
return mInstance;
}
Expand All @@ -49,8 +62,9 @@ public static PaymentConfiguration getInstance() {
* A publishable key from the Dashboard's
* <a href="https://dashboard.stripe.com/apikeys">API keys</a> page.
*/
public static void init(@NonNull String publishableKey) {
public static void init(@NonNull Context context, @NonNull String publishableKey) {
mInstance = new PaymentConfiguration(publishableKey);
new Store(context).save(publishableKey);
}

@NonNull
Expand Down Expand Up @@ -87,4 +101,33 @@ public boolean equals(@Nullable Object obj) {
private boolean typedEquals(@NonNull PaymentConfiguration obj) {
return ObjectUtils.equals(mPublishableKey, obj.mPublishableKey);
}

/**
* Manages saving and loading {@link PaymentConfiguration} data to SharedPreferences.
*/
private static final class Store {
@NonNull private final SharedPreferences mPrefs;
private static final String NAME = PaymentConfiguration.class.getCanonicalName();

private static final String KEY_PUBLISHABLE_KEY = "key_publishable_key";

private Store(@NonNull Context context) {
mPrefs = context.getApplicationContext().getSharedPreferences(NAME, 0);
}

private void save(@NonNull String publishableKey) {
mPrefs.edit()
.putString(KEY_PUBLISHABLE_KEY, publishableKey)
.apply();
}

@Nullable
private PaymentConfiguration load() {
final String publishableKey = mPrefs.getString(KEY_PUBLISHABLE_KEY, null);
if (publishableKey == null) {
return null;
}
return new PaymentConfiguration(publishableKey);
}
}
}
18 changes: 11 additions & 7 deletions stripe/src/main/java/com/stripe/android/PaymentSession.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IntRange;
Expand Down Expand Up @@ -36,6 +37,7 @@ public class PaymentSession {
private final ActivityStarter<PaymentFlowActivity, PaymentFlowActivityStarter.Args>
mPaymentFlowActivityStarter;

@NonNull private final Context mContext;
@NonNull private final CustomerSession mCustomerSession;
@NonNull private final PaymentSessionPrefs mPaymentSessionPrefs;
private PaymentSessionData mPaymentSessionData;
Expand All @@ -51,30 +53,32 @@ public class PaymentSession {
* passed back to this session.
*/
public PaymentSession(@NonNull Activity activity) {
this(CustomerSession.getInstance(),
this(activity.getApplicationContext(),
CustomerSession.getInstance(),
new PaymentMethodsActivityStarter(activity),
new PaymentFlowActivityStarter(activity),
new PaymentSessionData(),
new PaymentSessionPrefs(activity));
new PaymentSessionData(), new PaymentSessionPrefs(activity));
}

public PaymentSession(@NonNull Fragment fragment) {
this(CustomerSession.getInstance(),
this(fragment.requireContext().getApplicationContext(),
CustomerSession.getInstance(),
new PaymentMethodsActivityStarter(fragment),
new PaymentFlowActivityStarter(fragment),
new PaymentSessionData(),
new PaymentSessionPrefs(fragment.requireActivity()));
new PaymentSessionData(), new PaymentSessionPrefs(fragment.requireActivity()));
}

@VisibleForTesting
PaymentSession(
@NonNull Context context,
@NonNull CustomerSession customerSession,
@NonNull ActivityStarter<PaymentMethodsActivity, PaymentMethodsActivityStarter.Args>
paymentMethodsActivityStarter,
@NonNull ActivityStarter<PaymentFlowActivity, PaymentFlowActivityStarter.Args>
paymentFlowActivityStarter,
@NonNull PaymentSessionData paymentSessionData,
@NonNull PaymentSessionPrefs paymentSessionPrefs) {
mContext = context;
mCustomerSession = customerSession;
mPaymentMethodsActivityStarter = paymentMethodsActivityStarter;
mPaymentFlowActivityStarter = paymentFlowActivityStarter;
Expand Down Expand Up @@ -288,7 +292,7 @@ public void presentPaymentMethodSelection(boolean shouldRequirePostalCode,
getSelectedPaymentMethodId(userSelectedPaymentMethodId))
.setShouldRequirePostalCode(shouldRequirePostalCode)
.setIsPaymentSessionActive(true)
.setPaymentConfiguration(PaymentConfiguration.getInstance())
.setPaymentConfiguration(PaymentConfiguration.getInstance(mContext))
.build()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected void onCreate(Bundle savedInstanceState) {
if (args.paymentConfiguration != null) {
paymentConfiguration = args.paymentConfiguration;
} else {
paymentConfiguration = PaymentConfiguration.getInstance();
paymentConfiguration = PaymentConfiguration.getInstance(this);
}
mStripe = new Stripe(getApplicationContext(), paymentConfiguration.getPublishableKey());
mPaymentMethodType = args.paymentMethodType;
Expand Down
12 changes: 10 additions & 2 deletions stripe/src/test/java/com/stripe/android/GooglePayConfigTest.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package com.stripe.android;

import androidx.test.core.app.ApplicationProvider;

import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import static org.junit.Assert.assertEquals;

@RunWith(RobolectricTestRunner.class)
public class GooglePayConfigTest {

@Test
public void getTokenizationSpecification() throws JSONException {
PaymentConfiguration.init(ApiKeyFixtures.FAKE_PUBLISHABLE_KEY);
final JSONObject tokenizationSpec = new GooglePayConfig().getTokenizationSpecification();
PaymentConfiguration.init(ApplicationProvider.getApplicationContext(),
ApiKeyFixtures.FAKE_PUBLISHABLE_KEY);
final JSONObject tokenizationSpec =
new GooglePayConfig(ApplicationProvider.getApplicationContext())
.getTokenizationSpecification();
final JSONObject params = tokenizationSpec.getJSONObject("parameters");
assertEquals("stripe",
params.getString("gateway"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.stripe.android;

import androidx.test.core.app.ApplicationProvider;

import org.junit.Before;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
Expand All @@ -26,15 +28,21 @@ public void getInstance_beforeInit_throwsRuntimeException() {
assertThrows(IllegalStateException.class, new ThrowingRunnable() {
@Override
public void run() {
PaymentConfiguration.getInstance();
PaymentConfiguration.getInstance(ApplicationProvider.getApplicationContext());
}
});
}

@Test
public void getInstance_withPublicKey_returnsDefaultInstance() {
PaymentConfiguration.init(ApiKeyFixtures.FAKE_PUBLISHABLE_KEY);
public void getInstance_whenInstanceIsNull_loadsFromPrefs() {
PaymentConfiguration.init(ApplicationProvider.getApplicationContext(),
ApiKeyFixtures.FAKE_PUBLISHABLE_KEY);

PaymentConfiguration.clearInstance();

assertEquals(ApiKeyFixtures.FAKE_PUBLISHABLE_KEY,
PaymentConfiguration.getInstance().getPublishableKey());
PaymentConfiguration
.getInstance(ApplicationProvider.getApplicationContext())
.getPublishableKey());
}
}
Loading

0 comments on commit 777e2e8

Please sign in to comment.