-
Notifications
You must be signed in to change notification settings - Fork 629
/
CustomerSession.java
808 lines (726 loc) · 31.5 KB
/
CustomerSession.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
package com.stripe.android;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.LocalBroadcastManager;
import com.stripe.android.exception.StripeException;
import com.stripe.android.model.Customer;
import com.stripe.android.model.ShippingInformation;
import com.stripe.android.model.Source;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Represents a logged-in session of a single Customer.
*/
public class CustomerSession
implements EphemeralKeyManager.KeyManagerListener<CustomerEphemeralKey> {
public static final String ACTION_API_EXCEPTION = "action_api_exception";
public static final String EXTRA_EXCEPTION = "exception";
public static final String EVENT_SHIPPING_INFO_SAVED = "shipping_info_saved";
private static final String ACTION_ADD_SOURCE = "add_source";
private static final String ACTION_DELETE_SOURCE = "delete_source";
private static final String ACTION_SET_DEFAULT_SOURCE = "default_source";
private static final String ACTION_SET_CUSTOMER_SHIPPING_INFO = "set_shipping_info";
private static final String KEY_SOURCE = "source";
private static final String KEY_SOURCE_TYPE = "source_type";
private static final String KEY_SHIPPING_INFO = "shipping_info";
private static final String TOKEN_PAYMENT_SESSION = "PaymentSession";
private static final Set<String> VALID_TOKENS =
new HashSet<>(Arrays.asList("AddSourceActivity",
"PaymentMethodsActivity",
"PaymentFlowActivity",
TOKEN_PAYMENT_SESSION,
"ShippingInfoScreen",
"ShippingMethodScreen"));
private static final int CUSTOMER_RETRIEVED = 7;
private static final int CUSTOMER_ERROR = 11;
private static final int SOURCE_RETRIEVED = 13;
private static final int SOURCE_ERROR = 17;
private static final int CUSTOMER_SHIPPING_INFO_SAVED = 19;
// The maximum number of active threads we support
private static final int THREAD_POOL_SIZE = 3;
// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 2;
// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
private static final long KEY_REFRESH_BUFFER_IN_SECONDS = 30L;
private static final long CUSTOMER_CACHE_DURATION_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
private static CustomerSession mInstance;
@Nullable private Customer mCustomer;
private long mCustomerCacheTime;
@Nullable private Context mContext;
@NonNull private final Map<String, CustomerRetrievalListener> mCustomerRetrievalListeners =
new HashMap<>();
@NonNull private final Map<String, SourceRetrievalListener> mSourceRetrievalListeners =
new HashMap<>();
@NonNull private final EphemeralKeyManager mEphemeralKeyManager;
@NonNull private final Handler mUiThreadHandler;
@NonNull private final Set<String> mProductUsageTokens;
@Nullable private final Calendar mProxyNowCalendar;
@NonNull private final ThreadPoolExecutor mThreadPoolExecutor;
@NonNull private final StripeApiHandler mApiHandler;
/**
* Create a CustomerSession with the provided {@link EphemeralKeyProvider}.
*
* @param keyProvider an {@link EphemeralKeyProvider} used to get
* {@link CustomerEphemeralKey EphemeralKeys} as needed
*/
public static void initCustomerSession(@NonNull EphemeralKeyProvider keyProvider) {
setInstance(new CustomerSession(keyProvider));
}
/**
* Gets the singleton instance of {@link CustomerSession}. If the session has not been
* initialized, this will throw a {@link RuntimeException}.
*
* @return the singleton {@link CustomerSession} instance.
*/
@NonNull
public static CustomerSession getInstance() {
if (mInstance == null) {
throw new IllegalStateException(
"Attempted to get instance of CustomerSession without initialization.");
}
return mInstance;
}
@VisibleForTesting
static void setInstance(@Nullable CustomerSession customerSession) {
mInstance = customerSession;
}
/**
* End the singleton instance of a {@link CustomerSession}.
* Calls to {@link CustomerSession#getInstance()} will throw an {@link IllegalStateException}
* after this call, until the user calls
* {@link CustomerSession#initCustomerSession(EphemeralKeyProvider)} again.
*/
public static void endCustomerSession() {
clearInstance();
}
@VisibleForTesting
static void clearInstance() {
if (mInstance != null) {
mInstance.mCustomerRetrievalListeners.clear();
mInstance.mSourceRetrievalListeners.clear();
}
cancelCallbacks();
setInstance(null);
}
/**
* End any async calls in process and will not invoke callback listeners.
* It will not clear the singleton instance of a {@link CustomerSession} so it can be
* safely used when a view is being removed/destroyed to avoid null pointer exceptions
* due to async operation delay.
* No need to call {@link CustomerSession#initCustomerSession(EphemeralKeyProvider)} again
* after this operation.
*/
public static void cancelCallbacks() {
if (mInstance == null) {
return;
}
mInstance.mThreadPoolExecutor.shutdownNow();
}
private CustomerSession(@NonNull EphemeralKeyProvider keyProvider) {
this(keyProvider, null, createThreadPoolExecutor(), new StripeApiHandler());
}
@VisibleForTesting
CustomerSession(
@NonNull EphemeralKeyProvider keyProvider,
@Nullable Calendar proxyNowCalendar,
@NonNull ThreadPoolExecutor threadPoolExecutor,
@NonNull StripeApiHandler apiHandler) {
mThreadPoolExecutor = threadPoolExecutor;
mProxyNowCalendar = proxyNowCalendar;
mProductUsageTokens = new HashSet<>();
mApiHandler = apiHandler;
mUiThreadHandler = createMainThreadHandler();
mEphemeralKeyManager = new EphemeralKeyManager<>(
keyProvider,
this,
KEY_REFRESH_BUFFER_IN_SECONDS,
proxyNowCalendar,
CustomerEphemeralKey.class);
}
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void addProductUsageTokenIfValid(@Nullable String token) {
if (VALID_TOKENS.contains(token)) {
mProductUsageTokens.add(token);
}
}
/**
* Retrieve the current {@link Customer}. If the cached value at {@link #mCustomer} is not
* stale, this returns immediately with the cache. If not, it fetches a new value and returns
* that to the listener.
*
* @param listener a {@link CustomerRetrievalListener} to invoke with the result of getting the
* customer, either from the cache or from the server
*/
public void retrieveCurrentCustomer(@NonNull CustomerRetrievalListener listener) {
final Customer cachedCustomer = getCachedCustomer();
if (cachedCustomer != null) {
listener.onCustomerRetrieved(cachedCustomer);
} else {
mCustomer = null;
final String operationId = UUID.randomUUID().toString();
mCustomerRetrievalListeners.put(operationId, listener);
mEphemeralKeyManager.retrieveEphemeralKey(operationId, null, null);
}
}
/**
* Force an update of the current customer, regardless of how much time has passed.
*
* @param listener a {@link CustomerRetrievalListener} to invoke with the result of getting
* the customer from the server
*/
public void updateCurrentCustomer(@NonNull CustomerRetrievalListener listener) {
mCustomer = null;
final String operationId = UUID.randomUUID().toString();
mCustomerRetrievalListeners.put(operationId, listener);
mEphemeralKeyManager.retrieveEphemeralKey(operationId, null, null);
}
/**
* Gets a cached customer, or {@code null} if the current customer has expired.
*
* @return the current value of {@link #mCustomer}, or {@code null} if the customer object is
* expired.
*/
@Nullable
public Customer getCachedCustomer() {
if (canUseCachedCustomer()) {
return mCustomer;
} else {
return null;
}
}
/**
* Add the input source to the current customer object.
*
* @param context the {@link Context} to use for resources
* @param sourceId the ID of the source to be added
* @param listener a {@link SourceRetrievalListener} to be notified when the api call is
* complete
*/
public void addCustomerSource(
@NonNull Context context,
@NonNull String sourceId,
@NonNull @Source.SourceType String sourceType,
@Nullable SourceRetrievalListener listener) {
mContext = context.getApplicationContext();
final Map<String, Object> arguments = new HashMap<>();
arguments.put(KEY_SOURCE, sourceId);
arguments.put(KEY_SOURCE_TYPE, sourceType);
final String operationId = UUID.randomUUID().toString();
if (listener != null) {
mSourceRetrievalListeners.put(operationId, listener);
}
mEphemeralKeyManager.retrieveEphemeralKey(operationId, ACTION_ADD_SOURCE, arguments);
}
/**
* Delete the source from the current customer object.
* @param context the {@link Context} to use for resources
* @param sourceId the ID of the source to be deleted
* @param listener a {@link SourceRetrievalListener} to be notified when the api call is
* complete. The api call will return the removed source.
*/
public void deleteCustomerSource(
@NonNull Context context,
@NonNull String sourceId,
@Nullable SourceRetrievalListener listener) {
mContext = context.getApplicationContext();
Map<String, Object> arguments = new HashMap<>();
arguments.put(KEY_SOURCE, sourceId);
final String operationId = UUID.randomUUID().toString();
if (listener != null) {
mSourceRetrievalListeners.put(operationId, listener);
}
mEphemeralKeyManager.retrieveEphemeralKey(operationId, ACTION_DELETE_SOURCE, arguments);
}
/**
* Set the shipping information on the current customer object.
*
* @param context a {@link Context} to use for resources
* @param shippingInformation the data to be set
*/
public void setCustomerShippingInformation(
@NonNull Context context,
@NonNull ShippingInformation shippingInformation) {
mContext = context.getApplicationContext();
final Map<String, Object> arguments = new HashMap<>();
arguments.put(KEY_SHIPPING_INFO, shippingInformation);
mEphemeralKeyManager.retrieveEphemeralKey(null, ACTION_SET_CUSTOMER_SHIPPING_INFO,
arguments);
}
/**
* Set the default source of the current customer object.
*
* @param context a {@link Context} to use for resources
* @param sourceId the ID of the source to be set
* @param listener a {@link CustomerRetrievalListener} to be notified about an update to the
* customer
*/
public void setCustomerDefaultSource(
@NonNull Context context,
@NonNull String sourceId,
@NonNull @Source.SourceType String sourceType,
@Nullable CustomerRetrievalListener listener) {
mContext = context.getApplicationContext();
final Map<String, Object> arguments = new HashMap<>();
arguments.put(KEY_SOURCE, sourceId);
arguments.put(KEY_SOURCE_TYPE, sourceType);
final String operationId = UUID.randomUUID().toString();
if (listener != null) {
mCustomerRetrievalListeners.put(operationId, listener);
}
mEphemeralKeyManager.retrieveEphemeralKey(operationId, ACTION_SET_DEFAULT_SOURCE,
arguments);
}
void resetUsageTokens() {
mProductUsageTokens.clear();
}
@Nullable
@VisibleForTesting
Customer getCustomer() {
return mCustomer;
}
@VisibleForTesting
long getCustomerCacheTime() {
return mCustomerCacheTime;
}
@VisibleForTesting
Set<String> getProductUsageTokens() {
return mProductUsageTokens;
}
private void addCustomerSource(
@NonNull final Context context,
@NonNull final CustomerEphemeralKey key,
@NonNull final String sourceId,
@NonNull final String sourceType,
@Nullable final String operationId) {
final Runnable fetchCustomerRunnable = new Runnable() {
@Override
public void run() {
try {
final SourceMessage sourceMessage = new SourceMessage(
operationId,
addCustomerSourceWithKey(context, key, sourceId, sourceType)
);
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(SOURCE_RETRIEVED, sourceMessage));
} catch (StripeException stripeEx) {
mUiThreadHandler
.sendMessage(mUiThreadHandler.obtainMessage(SOURCE_ERROR,
new ExceptionMessage(operationId, stripeEx)));
sendErrorIntent(stripeEx);
}
}
};
executeRunnable(fetchCustomerRunnable);
}
private boolean canUseCachedCustomer() {
final long currentTime = getCalendarInstance().getTimeInMillis();
return mCustomer != null &&
currentTime - mCustomerCacheTime < CUSTOMER_CACHE_DURATION_MILLISECONDS;
}
private void deleteCustomerSource(
@NonNull final Context context,
@NonNull final CustomerEphemeralKey key,
@NonNull final String sourceId,
@Nullable final String operationId) {
final Runnable fetchCustomerRunnable = new Runnable() {
@Override
public void run() {
try {
final SourceMessage sourceMessage = new SourceMessage(
operationId,
deleteCustomerSourceWithKey(context, key, sourceId)
);
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(SOURCE_RETRIEVED, sourceMessage));
} catch (StripeException stripeEx) {
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(SOURCE_ERROR,
new ExceptionMessage(operationId, stripeEx)));
sendErrorIntent(stripeEx);
}
}
};
executeRunnable(fetchCustomerRunnable);
}
private void setCustomerSourceDefault(
@NonNull final Context context,
@NonNull final CustomerEphemeralKey key,
@NonNull final String sourceId,
@NonNull final String sourceType,
@Nullable final String operationId) {
final Runnable fetchCustomerRunnable = new Runnable() {
@Override
public void run() {
try {
final CustomerMessage customerMessage = new CustomerMessage(
operationId,
setCustomerSourceDefaultWithKey(context, key, sourceId, sourceType)
);
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(CUSTOMER_RETRIEVED, customerMessage));
} catch (StripeException stripeEx) {
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(CUSTOMER_ERROR,
new ExceptionMessage(operationId, stripeEx)));
sendErrorIntent(stripeEx);
}
}
};
executeRunnable(fetchCustomerRunnable);
}
private void setCustomerShippingInformation(
@NonNull final Context context,
@NonNull final CustomerEphemeralKey key,
@NonNull final ShippingInformation shippingInformation,
@Nullable final String operationId) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
try {
final CustomerMessage customerMessage = new CustomerMessage(
operationId,
setCustomerShippingInfoWithKey(context, key, shippingInformation)
);
mUiThreadHandler.sendMessage(mUiThreadHandler
.obtainMessage(CUSTOMER_SHIPPING_INFO_SAVED, customerMessage));
} catch (StripeException stripeEx) {
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(CUSTOMER_ERROR,
new ExceptionMessage(operationId, stripeEx)));
sendErrorIntent(stripeEx);
}
}
};
executeRunnable(runnable);
}
private void updateCustomer(@NonNull final CustomerEphemeralKey key,
@Nullable final String operationId) {
final Runnable fetchCustomerRunnable = new Runnable() {
@Override
public void run() {
try {
final CustomerMessage customerMessage = new CustomerMessage(
operationId,
retrieveCustomerWithKey(key)
);
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(CUSTOMER_RETRIEVED, customerMessage));
} catch (StripeException stripeEx) {
mUiThreadHandler.sendMessage(
mUiThreadHandler.obtainMessage(CUSTOMER_ERROR,
new ExceptionMessage(operationId, stripeEx)));
}
}
};
executeRunnable(fetchCustomerRunnable);
}
private void executeRunnable(@NonNull Runnable runnable) {
mThreadPoolExecutor.execute(runnable);
}
@Override
public void onKeyUpdate(
@NonNull CustomerEphemeralKey ephemeralKey,
@Nullable String operationId,
@Nullable String actionString,
@Nullable Map<String, Object> arguments) {
if (actionString == null) {
updateCustomer(ephemeralKey, operationId);
return;
}
if (arguments == null || mContext == null) {
return;
}
if (ACTION_ADD_SOURCE.equals(actionString) && arguments.containsKey(KEY_SOURCE) &&
arguments.containsKey(KEY_SOURCE_TYPE)) {
addCustomerSource(mContext,
ephemeralKey,
(String) arguments.get(KEY_SOURCE),
(String) arguments.get(KEY_SOURCE_TYPE),
operationId
);
resetUsageTokens();
} else if (ACTION_DELETE_SOURCE.equals(actionString) &&
arguments.containsKey(KEY_SOURCE)) {
deleteCustomerSource(mContext,
ephemeralKey,
(String) arguments.get(KEY_SOURCE),
operationId);
resetUsageTokens();
} else if (ACTION_SET_DEFAULT_SOURCE.equals(actionString)
&& arguments.containsKey(KEY_SOURCE) && arguments.containsKey(KEY_SOURCE_TYPE)) {
setCustomerSourceDefault(mContext,
ephemeralKey,
(String) arguments.get(KEY_SOURCE),
(String) arguments.get(KEY_SOURCE_TYPE),
operationId);
resetUsageTokens();
} else if (ACTION_SET_CUSTOMER_SHIPPING_INFO.equals(actionString) &&
arguments.containsKey(KEY_SHIPPING_INFO)) {
setCustomerShippingInformation(mContext,
ephemeralKey,
(ShippingInformation) arguments.get(KEY_SHIPPING_INFO),
operationId);
resetUsageTokens();
}
}
@Override
public void onKeyError(@Nullable String operationId, int httpCode,
@Nullable String errorMessage) {
// Any error eliminates all listeners
final CustomerRetrievalListener customerRetrievalListener =
getCustomerRetrievalListener(operationId);
if (customerRetrievalListener != null) {
customerRetrievalListener.onError(httpCode, errorMessage, null);
}
final SourceRetrievalListener sourceRetrievalListener =
getSourceRetrievalListener(operationId);
if (sourceRetrievalListener != null) {
sourceRetrievalListener.onError(httpCode, errorMessage, null);
}
}
@NonNull
private Handler createMainThreadHandler() {
return new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CUSTOMER_RETRIEVED: {
if (msg.obj instanceof CustomerMessage) {
final CustomerMessage customerMessage = (CustomerMessage) msg.obj;
final String operationId = customerMessage.operationId;
mCustomer = customerMessage.customer;
mCustomerCacheTime = getCalendarInstance().getTimeInMillis();
final CustomerRetrievalListener listener =
getCustomerRetrievalListener(operationId);
if (listener != null && mCustomer != null) {
listener.onCustomerRetrieved(mCustomer);
}
}
break;
}
case SOURCE_RETRIEVED: {
if (msg.obj instanceof SourceMessage) {
final SourceMessage sourceMessage = (SourceMessage) msg.obj;
final String operationId = sourceMessage.operationId;
final SourceRetrievalListener listener =
getSourceRetrievalListener(operationId);
if (listener != null && sourceMessage.source != null) {
listener.onSourceRetrieved(sourceMessage.source);
}
}
// Clear our context reference so we don't use a stale one.
mContext = null;
break;
}
case CUSTOMER_SHIPPING_INFO_SAVED: {
if (mContext != null && msg.obj instanceof CustomerMessage) {
final CustomerMessage customerMessage = (CustomerMessage) msg.obj;
mCustomer = customerMessage.customer;
LocalBroadcastManager.getInstance(mContext)
.sendBroadcast(new Intent(EVENT_SHIPPING_INFO_SAVED));
}
break;
}
case CUSTOMER_ERROR: {
if (msg.obj instanceof ExceptionMessage) {
final ExceptionMessage exceptionMessage = (ExceptionMessage) msg.obj;
handleRetrievalError(exceptionMessage.operationId,
exceptionMessage.exception, CUSTOMER_ERROR);
}
break;
}
case SOURCE_ERROR: {
if (msg.obj instanceof ExceptionMessage) {
final ExceptionMessage exceptionMessage = (ExceptionMessage) msg.obj;
handleRetrievalError(exceptionMessage.operationId,
exceptionMessage.exception, SOURCE_ERROR);
}
break;
}
default: {
break;
}
}
}
};
}
private void handleRetrievalError(@Nullable String operationId,
@NonNull StripeException exception,
int errorType) {
final RetrievalListener listener;
if (errorType == SOURCE_ERROR) {
listener = mSourceRetrievalListeners.remove(operationId);
} else if (errorType == CUSTOMER_ERROR) {
listener = mCustomerRetrievalListeners.remove(operationId);
} else {
listener = null;
}
if (listener != null) {
final int errorCode = exception.getStatusCode() == null
? 400
: exception.getStatusCode();
listener.onError(errorCode,
exception.getLocalizedMessage(),
exception.getStripeError());
}
resetUsageTokens();
}
@NonNull
private static ThreadPoolExecutor createThreadPoolExecutor() {
return new ThreadPoolExecutor(
THREAD_POOL_SIZE,
THREAD_POOL_SIZE,
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
new LinkedBlockingQueue<Runnable>());
}
@NonNull
private Calendar getCalendarInstance() {
return mProxyNowCalendar == null ? Calendar.getInstance() : mProxyNowCalendar;
}
@Nullable
private Source addCustomerSourceWithKey(
@NonNull Context context,
@NonNull CustomerEphemeralKey key,
@NonNull String sourceId,
@NonNull @Source.SourceType String sourceType) throws StripeException {
return mApiHandler.addCustomerSource(
context,
key.getCustomerId(),
PaymentConfiguration.getInstance().getPublishableKey(),
new ArrayList<>(mProductUsageTokens),
sourceId,
sourceType,
key.getSecret(),
null);
}
@Nullable
private Source deleteCustomerSourceWithKey(
@NonNull Context context,
@NonNull CustomerEphemeralKey key,
@NonNull String sourceId) throws StripeException {
return mApiHandler.deleteCustomerSource(
context,
key.getCustomerId(),
PaymentConfiguration.getInstance().getPublishableKey(),
new ArrayList<>(mProductUsageTokens),
sourceId,
key.getSecret(),
null);
}
@Nullable
private Customer setCustomerShippingInfoWithKey(
@NonNull Context context,
@NonNull CustomerEphemeralKey key,
@NonNull ShippingInformation shippingInformation) throws StripeException {
return mApiHandler.setCustomerShippingInfo(
context,
key.getCustomerId(),
PaymentConfiguration.getInstance().getPublishableKey(),
new ArrayList<>(mProductUsageTokens),
shippingInformation,
key.getSecret(),
null);
}
@Nullable
private Customer setCustomerSourceDefaultWithKey(
@NonNull Context context,
@NonNull CustomerEphemeralKey key,
@NonNull String sourceId,
@NonNull @Source.SourceType String sourceType) throws StripeException {
return mApiHandler.setDefaultCustomerSource(
context,
key.getCustomerId(),
PaymentConfiguration.getInstance().getPublishableKey(),
new ArrayList<>(mProductUsageTokens),
sourceId,
sourceType,
key.getSecret(),
null);
}
/**
* Calls the Stripe API (or a test proxy) to fetch a customer. If the provided key is expired,
* this method <b>does not</b> update the key.
* Use {@link #updateCustomer(CustomerEphemeralKey, String)} to validate the key
* before refreshing the customer.
*
* @param key the {@link CustomerEphemeralKey} used for this access
* @return a {@link Customer} if one can be found with this key, or {@code null} if one cannot.
*/
@Nullable
private Customer retrieveCustomerWithKey(@NonNull CustomerEphemeralKey key)
throws StripeException {
return mApiHandler.retrieveCustomer(key.getCustomerId(), key.getSecret());
}
private void sendErrorIntent(@NonNull StripeException exception) {
if (mContext == null) {
return;
}
final Bundle bundle = new Bundle();
bundle.putSerializable(EXTRA_EXCEPTION, exception);
final Intent intent = new Intent(ACTION_API_EXCEPTION)
.putExtras(bundle);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}
@Nullable
private CustomerRetrievalListener getCustomerRetrievalListener(@Nullable String operationId) {
return mCustomerRetrievalListeners.remove(operationId);
}
@Nullable
private SourceRetrievalListener getSourceRetrievalListener(@Nullable String operationId) {
return mSourceRetrievalListeners.remove(operationId);
}
public interface CustomerRetrievalListener extends RetrievalListener {
void onCustomerRetrieved(@NonNull Customer customer);
}
public interface SourceRetrievalListener extends RetrievalListener {
void onSourceRetrieved(@NonNull Source source);
}
interface RetrievalListener {
void onError(int errorCode, @Nullable String errorMessage,
@Nullable StripeError stripeError);
}
private static class CustomerMessage {
@Nullable private final String operationId;
@Nullable private final Customer customer;
private CustomerMessage(@Nullable String operationId, @Nullable Customer customer) {
this.operationId = operationId;
this.customer = customer;
}
}
private static class SourceMessage {
@Nullable private final String operationId;
@Nullable private final Source source;
private SourceMessage(@Nullable String operationId, @Nullable Source source) {
this.operationId = operationId;
this.source = source;
}
}
private static class ExceptionMessage {
@Nullable private final String operationId;
@NonNull private final StripeException exception;
private ExceptionMessage(@Nullable String operationId, @NonNull StripeException exception) {
this.operationId = operationId;
this.exception = exception;
}
}
}