@capgo/capacitor-pay 8.1.0 → 8.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,11 @@ package app.capgo.pay;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.Intent;
5
+ import androidx.activity.ComponentActivity;
6
+ import androidx.activity.result.ActivityResult;
7
+ import androidx.activity.result.ActivityResultLauncher;
8
+ import androidx.activity.result.IntentSenderRequest;
9
+ import androidx.activity.result.contract.ActivityResultContracts;
5
10
  import androidx.annotation.Nullable;
6
11
  import com.getcapacitor.JSObject;
7
12
  import com.getcapacitor.Logger;
@@ -10,6 +15,8 @@ import com.getcapacitor.PluginCall;
10
15
  import com.getcapacitor.PluginMethod;
11
16
  import com.getcapacitor.annotation.CapacitorPlugin;
12
17
  import com.google.android.gms.common.api.ApiException;
18
+ import com.google.android.gms.common.api.CommonStatusCodes;
19
+ import com.google.android.gms.common.api.ResolvableApiException;
13
20
  import com.google.android.gms.common.api.Status;
14
21
  import com.google.android.gms.tasks.Task;
15
22
  import com.google.android.gms.wallet.AutoResolveHelper;
@@ -26,11 +33,23 @@ import org.json.JSONObject;
26
33
  @CapacitorPlugin(name = "Pay")
27
34
  public class PayPlugin extends Plugin {
28
35
 
29
- private final String pluginVersion = "8.1.0";
36
+ private final String pluginVersion = "8.1.2";
30
37
 
31
- private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 8001;
38
+ private boolean paymentInProgress = false;
39
+ private boolean resolutionInProgress = false;
40
+ private ActivityResultLauncher<IntentSenderRequest> googlePayLauncher;
41
+ private PaymentsClient paymentsClient;
42
+ private int lastEnv = -1;
43
+ private PluginCall pendingGooglePayCall;
32
44
 
33
- private PluginCall pendingPaymentCall;
45
+ @Override
46
+ public void load() {
47
+ if (getActivity() == null) {
48
+ Logger.warn("PayPlugin", "Google Pay launcher unavailable during load(): no active activity.");
49
+ return;
50
+ }
51
+ this.ensureLauncher();
52
+ }
34
53
 
35
54
  @PluginMethod
36
55
  public void isPayAvailable(PluginCall call) {
@@ -80,11 +99,16 @@ public class PayPlugin extends Plugin {
80
99
 
81
100
  @PluginMethod
82
101
  public void requestPayment(PluginCall call) {
83
- if (pendingPaymentCall != null) {
102
+ if (this.paymentInProgress) {
84
103
  call.reject("Another Google Pay request is already in progress.");
85
104
  return;
86
105
  }
87
106
 
107
+ if (!ensureLauncher()) {
108
+ call.reject("No active activity to start Google Pay.");
109
+ return;
110
+ }
111
+
88
112
  JSObject googleOptions = call.getObject("google");
89
113
  if (googleOptions == null) {
90
114
  call.reject("Google Pay configuration is required on Android.");
@@ -111,77 +135,209 @@ public class PayPlugin extends Plugin {
111
135
 
112
136
  try {
113
137
  PaymentDataRequest request = PaymentDataRequest.fromJson(paymentRequestJson.toString());
114
- if (request == null) {
115
- call.reject("Invalid `paymentDataRequest`.");
116
- return;
117
- }
118
-
119
- pendingPaymentCall = call;
120
- Task<PaymentData> task = client.loadPaymentData(request);
121
- Activity activity = getActivity();
122
- if (activity == null) {
123
- pendingPaymentCall = null;
124
- call.reject("No active activity to present Google Pay.");
125
- return;
126
- }
127
- AutoResolveHelper.resolveTask(task, activity, LOAD_PAYMENT_DATA_REQUEST_CODE);
138
+ this.paymentInProgress = true;
139
+ this.pendingGooglePayCall = call;
140
+ client.loadPaymentData(request).addOnSuccessListener(this::emitAuthorized).addOnFailureListener(this::emitError);
128
141
  } catch (Exception ex) {
129
- pendingPaymentCall = null;
142
+ this.paymentInProgress = false;
143
+ this.pendingGooglePayCall = null;
130
144
  call.reject("Failed to launch Google Pay.", ex);
131
145
  }
132
146
  }
133
147
 
134
- @Override
135
- protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {
136
- super.handleOnActivityResult(requestCode, resultCode, data);
148
+ private boolean ensureLauncher() {
149
+ if (googlePayLauncher != null) return true;
150
+
151
+ ComponentActivity activity = getActivity();
152
+ if (activity == null) {
153
+ return false;
154
+ }
155
+
156
+ try {
157
+ googlePayLauncher = activity.registerForActivityResult(
158
+ new ActivityResultContracts.StartIntentSenderForResult(),
159
+ this::handleGooglePayResult
160
+ );
161
+ return true;
162
+ } catch (IllegalStateException ex) {
163
+ Logger.warn("PayPlugin", "Failed to register Google Pay launcher: " + ex.getLocalizedMessage());
164
+ googlePayLauncher = null;
165
+ return false;
166
+ }
167
+ }
168
+
169
+ private void handleGooglePayResult(ActivityResult activityResult) {
170
+ resolutionInProgress = false;
171
+ paymentInProgress = false; // important: user interaction ended
172
+
173
+ int resultCode = activityResult.getResultCode();
174
+ Intent data = activityResult.getData();
137
175
 
138
- if (requestCode != LOAD_PAYMENT_DATA_REQUEST_CODE) {
176
+ Logger.debug("PayPlugin", "GooglePay resultCode=" + resultCode);
177
+
178
+ if (resultCode == Activity.RESULT_OK) {
179
+ emitAuthorizedFromIntent(data);
139
180
  return;
140
181
  }
141
182
 
142
- if (pendingPaymentCall == null) {
183
+ if (resultCode == Activity.RESULT_CANCELED) {
184
+ this.emitCancel();
143
185
  return;
144
186
  }
145
187
 
146
- PluginCall call = pendingPaymentCall;
147
- pendingPaymentCall = null;
188
+ if (resultCode == AutoResolveHelper.RESULT_ERROR) {
189
+ Status status = AutoResolveHelper.getStatusFromIntent(data);
190
+ String message = (status != null && status.getStatusMessage() != null)
191
+ ? status.getStatusMessage()
192
+ : "Google Pay returned an error.";
193
+ this.emitError(new Exception(message), "GOOGLE_PAY_API_ERROR", status);
194
+ return;
195
+ }
148
196
 
149
- if (resultCode == Activity.RESULT_OK) {
150
- if (data == null) {
151
- call.reject("Google Pay returned no data.");
152
- return;
153
- }
197
+ String message = "Google Pay returned unexpected result code: " + resultCode;
198
+ Exception ex = new Exception(message);
199
+ this.emitError(ex, "UNEXPECTED_ACTIVITY_RESULT");
200
+ }
201
+
202
+ private void emitAuthorizedFromIntent(Intent data) {
203
+ if (data == null) {
204
+ Exception ex = new Exception("Google Pay returned no data.");
205
+ this.emitError(ex, "NO_RESULT_DATA");
206
+ return;
207
+ }
208
+
209
+ PaymentData paymentData = PaymentData.getFromIntent(data);
210
+ if (paymentData == null) {
211
+ Exception ex = new Exception("Google Pay returned empty payment data.");
212
+ this.emitError(ex, "EMPTY_PAYMENT_DATA");
213
+ return;
214
+ }
215
+
216
+ emitAuthorized(paymentData);
217
+ }
218
+
219
+ private void emitAuthorized(PaymentData paymentData) {
220
+ try {
221
+ String json = paymentData.toJson();
222
+ JSONObject paymentDataJson = new JSONObject(json);
223
+
224
+ JSObject googleResult = new JSObject();
225
+ googleResult.put("paymentData", JSObject.fromJSONObject(paymentDataJson));
226
+
227
+ JSObject result = new JSObject();
228
+ result.put("platform", "android");
229
+ result.put("google", googleResult);
154
230
 
155
- PaymentData paymentData = PaymentData.getFromIntent(data);
156
- if (paymentData == null) {
157
- call.reject("Google Pay returned empty payment data.");
231
+ Logger.debug("PayPlugin", "Payment authorized");
232
+ resolvePendingGooglePayCall(result);
233
+
234
+ this.paymentInProgress = false;
235
+ } catch (JSONException ex) {
236
+ this.emitError(ex, "PARSE_ERROR");
237
+ }
238
+ }
239
+
240
+ private void emitError(Exception ex) {
241
+ this.emitError(ex, null, null);
242
+ }
243
+
244
+ private void emitError(Exception ex, String reason) {
245
+ this.emitError(ex, reason, null);
246
+ }
247
+
248
+ private void emitError(Exception ex, String reason, @Nullable Status status) {
249
+ if (ex instanceof ResolvableApiException rae) {
250
+ if (this.resolutionInProgress) {
251
+ // don't relaunch; treat as error
252
+ this.resolutionInProgress = false;
253
+ } else {
254
+ this.resolutionInProgress = true;
255
+ IntentSenderRequest isr = new IntentSenderRequest.Builder(rae.getResolution()).build();
256
+ googlePayLauncher.launch(isr);
158
257
  return;
159
258
  }
259
+ }
160
260
 
161
- try {
162
- String json = paymentData.toJson();
163
- JSONObject paymentDataJson = json != null ? new JSONObject(json) : new JSONObject();
164
- JSObject googleResult = new JSObject();
165
- googleResult.put("paymentData", JSObject.fromJSONObject(paymentDataJson));
261
+ this.paymentInProgress = false;
262
+ this.resolutionInProgress = false;
166
263
 
167
- JSObject result = new JSObject();
168
- result.put("platform", "android");
169
- result.put("google", googleResult);
264
+ JSObject error = new JSObject();
265
+ String message = ex.getMessage() != null ? ex.getMessage() : "Google Pay failed.";
170
266
 
171
- call.resolve(result);
172
- } catch (JSONException ex) {
173
- call.reject("Failed to parse Google Pay result.", ex);
267
+ if (ex instanceof JSONException) {
268
+ message = "Failed to parse Google Pay result.";
269
+ }
270
+
271
+ error.put("message", message);
272
+ error.put("platform", "android");
273
+ error.put("statusCode", "ERROR");
274
+ error.put("reason", reason != null ? reason : inferErrorReason(ex));
275
+
276
+ Integer googleStatusCode = null;
277
+ String googleStatusMessage = null;
278
+ if (status != null) {
279
+ googleStatusCode = status.getStatusCode();
280
+ googleStatusMessage = status.getStatusMessage();
281
+ } else if (ex instanceof ApiException apiException) {
282
+ googleStatusCode = apiException.getStatusCode();
283
+ googleStatusMessage = apiException.getStatusMessage();
284
+ }
285
+
286
+ if (googleStatusCode != null) {
287
+ error.put("googleStatusCode", googleStatusCode);
288
+ error.put("googleStatusCodeName", CommonStatusCodes.getStatusCodeString(googleStatusCode));
289
+ }
290
+ if (googleStatusMessage != null) {
291
+ error.put("googleStatusMessage", googleStatusMessage);
292
+ }
293
+ if (ex instanceof ResolvableApiException) {
294
+ error.put("resolvable", true);
295
+ }
296
+
297
+ Logger.error("PayPlugin", message, ex);
298
+ rejectPendingGooglePayCall(message, reason != null ? reason : inferErrorReason(ex), ex, error);
299
+ }
300
+
301
+ private void emitCancel() {
302
+ this.paymentInProgress = false;
303
+ JSObject result = new JSObject();
304
+ String message = "Payment canceled";
305
+ result.put("message", message);
306
+ result.put("platform", "android");
307
+ result.put("statusCode", "CANCELED");
308
+ result.put("reason", "CANCELED");
309
+
310
+ Logger.debug("PayPlugin", message);
311
+ rejectPendingGooglePayCall(message, "CANCELED", null, result);
312
+ }
313
+
314
+ private String inferErrorReason(Exception ex) {
315
+ if (ex instanceof JSONException) {
316
+ return "PARSE_ERROR";
317
+ }
318
+ if (ex instanceof ApiException) {
319
+ return "GOOGLE_PAY_API_ERROR";
320
+ }
321
+ return "UNKNOWN";
322
+ }
323
+
324
+ private void resolvePendingGooglePayCall(JSObject result) {
325
+ PluginCall call = this.pendingGooglePayCall;
326
+ this.pendingGooglePayCall = null;
327
+ if (call != null) {
328
+ call.resolve(result);
329
+ }
330
+ }
331
+
332
+ private void rejectPendingGooglePayCall(String message, @Nullable String code, @Nullable Exception error, @Nullable JSObject data) {
333
+ PluginCall call = this.pendingGooglePayCall;
334
+ this.pendingGooglePayCall = null;
335
+ if (call != null) {
336
+ if (error != null) {
337
+ call.reject(message, code, error, data);
338
+ } else {
339
+ call.reject(message, code, data);
174
340
  }
175
- } else if (resultCode == Activity.RESULT_CANCELED) {
176
- call.reject("Payment canceled.");
177
- } else if (resultCode == AutoResolveHelper.RESULT_ERROR) {
178
- Status status = AutoResolveHelper.getStatusFromIntent(data);
179
- String message = status != null && status.getStatusMessage() != null
180
- ? status.getStatusMessage()
181
- : "Google Pay returned an error.";
182
- call.reject(message);
183
- } else {
184
- call.reject("Unexpected activity result: " + resultCode);
185
341
  }
186
342
  }
187
343
 
@@ -198,8 +354,12 @@ public class PayPlugin extends Plugin {
198
354
  }
199
355
 
200
356
  private PaymentsClient createPaymentsClient(int environment) {
201
- Wallet.WalletOptions options = new Wallet.WalletOptions.Builder().setEnvironment(environment).build();
202
- return Wallet.getPaymentsClient(getContext(), options);
357
+ if (paymentsClient == null || lastEnv != environment) {
358
+ Wallet.WalletOptions options = new Wallet.WalletOptions.Builder().setEnvironment(environment).build();
359
+ paymentsClient = Wallet.getPaymentsClient(getContext(), options);
360
+ lastEnv = environment;
361
+ }
362
+ return paymentsClient;
203
363
  }
204
364
 
205
365
  private int parseEnvironment(@Nullable String environment) {