-
Notifications
You must be signed in to change notification settings - Fork 629
/
RedirectActivity.java
293 lines (263 loc) · 11.8 KB
/
RedirectActivity.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
package com.stripe.example.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import com.stripe.android.PaymentConfiguration;
import com.stripe.android.Stripe;
import com.stripe.android.model.Card;
import com.stripe.android.model.Source;
import com.stripe.android.model.SourceCardData;
import com.stripe.android.model.SourceParams;
import com.stripe.android.view.CardInputWidget;
import com.stripe.example.R;
import com.stripe.example.adapter.RedirectAdapter;
import com.stripe.example.controller.ErrorDialogHandler;
import com.stripe.example.controller.ProgressDialogController;
import com.stripe.example.controller.RedirectDialogController;
import java.util.concurrent.Callable;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
/**
* Activity that lets you redirect for a 3DS source verification.
*/
public class RedirectActivity extends AppCompatActivity {
private static final String RETURN_SCHEMA = "stripe://";
private static final String RETURN_HOST_ASYNC = "async";
private static final String RETURN_HOST_SYNC = "sync";
private static final String QUERY_CLIENT_SECRET = "client_secret";
private static final String QUERY_SOURCE_ID = "source";
@NonNull private final CompositeSubscription mCompositeSubscription =
new CompositeSubscription();
private CardInputWidget mCardInputWidget;
private RedirectAdapter mRedirectAdapter;
private ErrorDialogHandler mErrorDialogHandler;
private RedirectDialogController mRedirectDialogController;
private Source mRedirectSource;
private ProgressDialogController mProgressDialogController;
private Stripe mStripe;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_polling);
mCardInputWidget = findViewById(R.id.card_widget_three_d);
mErrorDialogHandler = new ErrorDialogHandler(this.getSupportFragmentManager());
mProgressDialogController = new ProgressDialogController(this.getSupportFragmentManager(),
getResources());
mRedirectDialogController = new RedirectDialogController(this);
mStripe = new Stripe(getApplicationContext());
Button threeDSecureButton = findViewById(R.id.btn_three_d_secure);
threeDSecureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
beginSequence();
}
});
Button threeDSyncButton = findViewById(R.id.btn_three_d_secure_sync);
threeDSyncButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
beginSequence();
}
});
RecyclerView recyclerView = findViewById(R.id.recycler_view);
RecyclerView.LayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(linearLayoutManager);
mRedirectAdapter = new RedirectAdapter();
recyclerView.setAdapter(mRedirectAdapter);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getData() != null && intent.getData().getQuery() != null) {
// The client secret and source ID found here is identical to
// that of the source used to get the redirect URL.
String clientSecret = intent.getData().getQueryParameter(QUERY_CLIENT_SECRET);
String sourceId = intent.getData().getQueryParameter(QUERY_SOURCE_ID);
if (clientSecret != null
&& sourceId != null
&& clientSecret.equals(mRedirectSource.getClientSecret())
&& sourceId.equals(mRedirectSource.getId())) {
updateSourceList(mRedirectSource);
mRedirectSource = null;
}
mRedirectDialogController.dismissDialog();
}
}
@Override
protected void onDestroy() {
mCompositeSubscription.unsubscribe();
super.onDestroy();
}
void beginSequence() {
Card displayCard = mCardInputWidget.getCard();
if (displayCard == null) {
return;
}
createCardSource(displayCard);
}
/**
* To start the 3DS cycle, create a {@link Source} out of the user-entered {@link Card}.
*
* @param card the {@link Card} used to create a source
*/
void createCardSource(@NonNull Card card) {
final SourceParams cardSourceParams = SourceParams.createCardParams(card);
final Observable<Source> cardSourceObservable =
Observable.fromCallable(
new Callable<Source>() {
@Override
public Source call() throws Exception {
return mStripe.createSourceSynchronous(
cardSourceParams,
PaymentConfiguration.getInstance().getPublishableKey());
}
});
mCompositeSubscription.add(cardSourceObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Action0() {
@Override
public void call() {
mProgressDialogController.show(R.string.createSource);
}
})
.subscribe(
// Because we've made the mapping above, we're now subscribing
// to the result of creating a 3DS Source
new Action1<Source>() {
@Override
public void call(Source source) {
SourceCardData sourceCardData =
(SourceCardData) source.getSourceTypeModel();
// Making a note of the Card Source in our list.
mRedirectAdapter.addItem(
source.getStatus(),
sourceCardData.getThreeDSecureStatus(),
source.getId(),
source.getType());
// If we need to get 3DS verification for this card, we
// first create a 3DS Source.
if (SourceCardData.REQUIRED.equals(
sourceCardData.getThreeDSecureStatus())) {
// The card Source can be used to create a 3DS Source
createThreeDSecureSource(source.getId());
} else {
mProgressDialogController.dismiss();
}
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mErrorDialogHandler.show(throwable.getMessage());
}
}
));
}
/**
* Create the 3DS Source as a separate call to the API. This is what is needed
* to verify the third-party approval. The only information from the Card source
* that is used is the ID field.
*
* @param sourceId the {@link Source#mId} from the {@link Card}-created {@link Source}.
*/
void createThreeDSecureSource(String sourceId) {
// This represents a request for a 3DS purchase of 10.00 euro.
final SourceParams threeDParams = SourceParams.createThreeDSecureParams(
1000L,
"EUR",
getUrl(true),
sourceId);
Observable<Source> threeDSecureObservable = Observable.fromCallable(
new Callable<Source>() {
@Override
public Source call() throws Exception {
return mStripe.createSourceSynchronous(
threeDParams,
PaymentConfiguration.getInstance().getPublishableKey());
}
});
mCompositeSubscription.add(threeDSecureObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
mProgressDialogController.dismiss();
}
})
.subscribe(
// Because we've made the mapping above, we're now subscribing
// to the result of creating a 3DS Source
new Action1<Source>() {
@Override
public void call(Source source) {
// Once a 3DS Source is created, that is used
// to initiate the third-party verification
showDialog(source);
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mErrorDialogHandler.show(throwable.getMessage());
}
}
));
}
/**
* Show a dialog with a link to the external verification site.
*
* @param source the {@link Source} to verify
*/
void showDialog(final Source source) {
// Caching the source object here because this app makes a lot of them.
mRedirectSource = source;
mRedirectDialogController.showDialog(source.getRedirect().getUrl());
}
private void updateSourceList(@Nullable Source source) {
if (source == null) {
mRedirectAdapter.addItem(
"No source found",
"Stopped",
"Error",
"None");
return;
}
mRedirectAdapter.addItem(
source.getStatus(),
"complete",
source.getId(),
source.getType());
}
/**
* Helper method to determine the return URL we use. This is one way to return basic information
* to the activity (via the return Intent's host field). Because polling has been deprecated,
* we no longer use this parameter in the example application, but it is used here to see the
* relationship with the returned value for any parameters you may want to send.
*
* @param isSync whether or not to use a URL that tells us to use a sync method when we come
* back to the application
* @return a return url to be sent to the vendor
*/
private static String getUrl(boolean isSync) {
if (isSync) {
return RETURN_SCHEMA + RETURN_HOST_SYNC;
} else {
return RETURN_SCHEMA + RETURN_HOST_ASYNC;
}
}
}