expo-pay 0.1.1 → 1.0.0

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.
Files changed (51) hide show
  1. package/README.md +211 -0
  2. package/android/build.gradle +8 -4
  3. package/android/src/main/AndroidManifest.xml +6 -1
  4. package/android/src/main/java/expo/modules/googlepay/ExpoGooglePayModule.kt +119 -0
  5. package/android/src/main/java/expo/modules/googlepay/ExpoGooglePayView.kt +475 -0
  6. package/build/ExpoPay.types.d.ts +46 -7
  7. package/build/ExpoPay.types.d.ts.map +1 -1
  8. package/build/ExpoPay.types.js.map +1 -1
  9. package/build/ExpoPayModule.d.ts +6 -8
  10. package/build/ExpoPayModule.d.ts.map +1 -1
  11. package/build/ExpoPayModule.js +6 -2
  12. package/build/ExpoPayModule.js.map +1 -1
  13. package/build/ExpoPayModule.web.d.ts +5 -4
  14. package/build/ExpoPayModule.web.d.ts.map +1 -1
  15. package/build/ExpoPayModule.web.js +6 -4
  16. package/build/ExpoPayModule.web.js.map +1 -1
  17. package/build/ExpoPayView.d.ts +7 -3
  18. package/build/ExpoPayView.d.ts.map +1 -1
  19. package/build/ExpoPayView.js +21 -5
  20. package/build/ExpoPayView.js.map +1 -1
  21. package/build/ExpoPayView.web.d.ts +2 -2
  22. package/build/ExpoPayView.web.d.ts.map +1 -1
  23. package/build/ExpoPayView.web.js +2 -3
  24. package/build/ExpoPayView.web.js.map +1 -1
  25. package/build/GooglePay.d.ts +5 -0
  26. package/build/GooglePay.d.ts.map +1 -0
  27. package/build/GooglePay.js +7 -0
  28. package/build/GooglePay.js.map +1 -0
  29. package/build/GooglePayJson.d.ts +3 -0
  30. package/build/GooglePayJson.d.ts.map +1 -0
  31. package/build/GooglePayJson.js +4 -0
  32. package/build/GooglePayJson.js.map +1 -0
  33. package/build/index.d.ts +3 -3
  34. package/build/index.d.ts.map +1 -1
  35. package/build/index.js +2 -5
  36. package/build/index.js.map +1 -1
  37. package/expo-module.config.json +1 -1
  38. package/package.json +37 -5
  39. package/src/ExpoPay.types.ts +75 -7
  40. package/src/ExpoPayModule.ts +20 -8
  41. package/src/ExpoPayModule.web.ts +14 -5
  42. package/src/ExpoPayView.tsx +65 -6
  43. package/src/ExpoPayView.web.tsx +3 -4
  44. package/src/GooglePay.ts +15 -0
  45. package/src/GooglePayJson.ts +5 -0
  46. package/src/index.ts +13 -5
  47. package/.prettierrc +0 -8
  48. package/android/src/main/java/expo/modules/pay/ExpoPayModule.kt +0 -29
  49. package/android/src/main/java/expo/modules/pay/ExpoPayView.kt +0 -26
  50. package/eslint.config.cjs +0 -5
  51. package/tsconfig.json +0 -28
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # expo-pay
2
+
3
+ Android Google Pay for Expo native apps.
4
+
5
+ `expo-pay` provides an Expo native module and native view for rendering the
6
+ official Google Pay button, checking `isReadyToPay`, and presenting the Android
7
+ Google Pay payment sheet. It does not process payments or talk to your backend:
8
+ you provide the Google Pay request JSON for your gateway, and your server or
9
+ payment provider handles the returned token.
10
+
11
+ Apple Pay is intentionally not implemented yet.
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ npm install expo-pay
17
+ ```
18
+
19
+ This package contains Android native code, so it must be used in an Expo
20
+ development build or a prebuilt/bare React Native app. It will not run inside
21
+ Expo Go.
22
+
23
+ ```sh
24
+ npx expo prebuild
25
+ npx expo run:android
26
+ ```
27
+
28
+ The Android module includes the Google Pay manifest metadata required by
29
+ Google:
30
+
31
+ ```xml
32
+ <meta-data
33
+ android:name="com.google.android.gms.wallet.api.enabled"
34
+ android:value="true" />
35
+ ```
36
+
37
+ ## Basic Usage
38
+
39
+ ```tsx
40
+ import GooglePayButton, { isReadyToPayAsync } from "expo-pay";
41
+ import { useRef, useState } from "react";
42
+ import type { GooglePayButtonRef } from "expo-pay";
43
+
44
+ const baseCardPaymentMethod = {
45
+ type: "CARD",
46
+ parameters: {
47
+ allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
48
+ allowedCardNetworks: ["AMEX", "DISCOVER", "MASTERCARD", "VISA"],
49
+ },
50
+ };
51
+
52
+ const paymentRequest = {
53
+ apiVersion: 2,
54
+ apiVersionMinor: 0,
55
+ allowedPaymentMethods: [
56
+ {
57
+ ...baseCardPaymentMethod,
58
+ tokenizationSpecification: {
59
+ type: "PAYMENT_GATEWAY",
60
+ parameters: {
61
+ gateway: "example",
62
+ gatewayMerchantId: "exampleGatewayMerchantId",
63
+ },
64
+ },
65
+ },
66
+ ],
67
+ merchantInfo: {
68
+ merchantName: "Example Merchant",
69
+ },
70
+ transactionInfo: {
71
+ totalPriceStatus: "FINAL",
72
+ totalPrice: "10.00",
73
+ currencyCode: "USD",
74
+ countryCode: "US",
75
+ },
76
+ };
77
+
78
+ const isReadyToPayRequest = {
79
+ apiVersion: 2,
80
+ apiVersionMinor: 0,
81
+ allowedPaymentMethods: [baseCardPaymentMethod],
82
+ };
83
+
84
+ export function Checkout() {
85
+ const ref = useRef<GooglePayButtonRef>(null);
86
+ const [ready, setReady] = useState<boolean | null>(null);
87
+
88
+ async function checkReadiness() {
89
+ setReady(await isReadyToPayAsync(isReadyToPayRequest));
90
+ }
91
+
92
+ return (
93
+ <GooglePayButton
94
+ ref={ref}
95
+ style={{ height: 48, width: "100%" }}
96
+ paymentRequest={paymentRequest}
97
+ isReadyToPayRequest={isReadyToPayRequest}
98
+ environment="TEST"
99
+ buttonTheme="dark"
100
+ buttonType="buy"
101
+ onReadyToPayChanged={({ nativeEvent }) => {
102
+ setReady(nativeEvent.isReadyToPay);
103
+ }}
104
+ onTokenReceived={({ nativeEvent }) => {
105
+ // Send nativeEvent.token to your backend/payment gateway.
106
+ }}
107
+ onCancel={() => {}}
108
+ onError={({ nativeEvent }) => {
109
+ console.warn(nativeEvent.code, nativeEvent.message);
110
+ }}
111
+ />
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## API
117
+
118
+ ### `isReadyToPayAsync(request, environment?)`
119
+
120
+ Checks whether Google Pay is available for the supplied request.
121
+
122
+ - `request`: raw Google Pay `IsReadyToPayRequest` JSON as a string or object.
123
+ - `environment`: `"TEST"` or `"PRODUCTION"`. Defaults to `"TEST"`.
124
+ - Returns `false` on unsupported platforms.
125
+
126
+ ### `GooglePayButton`
127
+
128
+ Renders the native Google Pay button and presents the payment sheet when
129
+ pressed.
130
+
131
+ Props:
132
+
133
+ - `paymentRequest`: required Google Pay `PaymentDataRequest` JSON as a string or
134
+ object.
135
+ - `isReadyToPayRequest`: optional explicit readiness request. If omitted, the
136
+ module derives one from `paymentRequest.allowedPaymentMethods`.
137
+ - `environment`: `"TEST"` or `"PRODUCTION"`. Defaults to `"TEST"`.
138
+ - `buttonTheme`: `"dark"` or `"light"`.
139
+ - `buttonType`: `"book"`, `"buy"`, `"checkout"`, `"donate"`, `"ewallet"`,
140
+ `"order"`, `"pay"`, `"pix"`, `"plain"`, or `"subscribe"`.
141
+ - `cornerRadius`: native Google Pay button corner radius.
142
+ - `disabled`: disables button presses.
143
+ - `existingPaymentMethodRequired`: adds
144
+ `existingPaymentMethodRequired: true` to the derived readiness request.
145
+ - `autoCheckReadiness`: checks readiness after prop updates. Defaults to `true`.
146
+ - `hideWhenNotReady`: hides the button when readiness is `false`.
147
+
148
+ Events:
149
+
150
+ - `onReadyToPayChanged`: `{ isReadyToPay }`
151
+ - `onTokenReceived`: `{ token, paymentData, paymentMethodData, paymentMethodType,
152
+ cardNetwork, cardDetails, email }`
153
+ - `onCancel`
154
+ - `onError`: `{ code, message, nativeMessage, statusCode, statusMessage }`
155
+
156
+ Ref methods:
157
+
158
+ - `presentPaymentSheet(): Promise<void>`
159
+ - `checkReadiness(): Promise<void>`
160
+
161
+ ## TEST and PRODUCTION
162
+
163
+ Use `"TEST"` while developing. Google's TEST environment returns fake,
164
+ non-chargeable payment credentials and does not require a Google Pay merchant
165
+ approval.
166
+
167
+ Before using `"PRODUCTION"`:
168
+
169
+ - Replace the example gateway values with your payment processor values.
170
+ - Use your real Google Pay merchant name and merchant ID where required.
171
+ - Complete Google's production access and integration checklist.
172
+ - Distribute the Android app through a production-signed build; Google Pay
173
+ production integrations are reviewed by Google.
174
+
175
+ Google references:
176
+
177
+ - [Android setup](https://developers.google.com/pay/api/android/guides/setup)
178
+ - [Android tutorial](https://developers.google.com/pay/api/android/guides/tutorial)
179
+ - [Test and deploy](https://developers.google.com/pay/api/android/guides/test-and-deploy/integration-checklist)
180
+
181
+ Expo references:
182
+
183
+ - [Native module tutorial](https://docs.expo.dev/modules/native-module-tutorial/)
184
+ - [Native view tutorial](https://docs.expo.dev/modules/native-view-tutorial/)
185
+ - [Module config](https://docs.expo.dev/modules/module-config/)
186
+
187
+ ## Troubleshooting
188
+
189
+ `Cannot find native module 'ExpoGooglePay'`
190
+
191
+ Rebuild the native app after installing the package:
192
+
193
+ ```sh
194
+ npx expo prebuild
195
+ npx expo run:android
196
+ ```
197
+
198
+ `GooglePayButton is only available on Android`
199
+
200
+ This package currently implements Android Google Pay only. Guard rendering with
201
+ `Platform.OS === "android"` in shared screens.
202
+
203
+ `ERR_GOOGLE_PAY_INVALID_REQUEST`
204
+
205
+ The request JSON could not be parsed by Google Pay. Validate the shape against
206
+ Google's `PaymentDataRequest` and `IsReadyToPayRequest` docs.
207
+
208
+ `ERR_GOOGLE_PAY_READY_TO_PAY`
209
+
210
+ The readiness check failed at the Google Pay API layer. Check Google Play
211
+ services availability, Android emulator/device configuration, and request JSON.
@@ -3,16 +3,20 @@ plugins {
3
3
  id 'expo-module-gradle-plugin'
4
4
  }
5
5
 
6
- group = 'expo.modules.pay'
7
- version = '0.1.0'
6
+ group = 'expo.modules.googlepay'
7
+ version = '0.1.1'
8
8
 
9
9
  android {
10
- namespace "expo.modules.pay"
10
+ namespace "expo.modules.googlepay"
11
11
  defaultConfig {
12
12
  versionCode 1
13
- versionName "0.1.0"
13
+ versionName "0.1.1"
14
14
  }
15
15
  lintOptions {
16
16
  abortOnError false
17
17
  }
18
18
  }
19
+
20
+ dependencies {
21
+ implementation "com.google.android.gms:play-services-wallet:20.0.0"
22
+ }
@@ -1,2 +1,7 @@
1
- <manifest>
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <application>
3
+ <meta-data
4
+ android:name="com.google.android.gms.wallet.api.enabled"
5
+ android:value="true" />
6
+ </application>
2
7
  </manifest>
@@ -0,0 +1,119 @@
1
+ package expo.modules.googlepay
2
+
3
+ import com.google.android.gms.wallet.IsReadyToPayRequest
4
+ import com.google.android.gms.wallet.Wallet
5
+ import expo.modules.kotlin.Promise
6
+ import expo.modules.kotlin.exception.Exceptions
7
+ import expo.modules.kotlin.modules.Module
8
+ import expo.modules.kotlin.modules.ModuleDefinition
9
+ import org.json.JSONException
10
+
11
+ class ExpoGooglePayModule : Module() {
12
+ override fun definition() = ModuleDefinition {
13
+ Name("ExpoGooglePay")
14
+
15
+ AsyncFunction("isReadyToPayAsync") { requestJson: String, environment: String?, promise: Promise ->
16
+ val request = try {
17
+ IsReadyToPayRequest.fromJson(requestJson)
18
+ } catch (exception: JSONException) {
19
+ promise.reject("ERR_GOOGLE_PAY_INVALID_REQUEST", "Invalid IsReadyToPayRequest JSON.", exception)
20
+ return@AsyncFunction
21
+ }
22
+
23
+ val activity = appContext.currentActivity
24
+ if (activity == null) {
25
+ promise.reject(Exceptions.MissingActivity())
26
+ return@AsyncFunction
27
+ }
28
+
29
+ val paymentsClient = Wallet.getPaymentsClient(
30
+ activity,
31
+ Wallet.WalletOptions.Builder()
32
+ .setEnvironment(parseGooglePayEnvironment(environment))
33
+ .build()
34
+ )
35
+
36
+ paymentsClient.isReadyToPay(request)
37
+ .addOnCompleteListener { task ->
38
+ if (task.isSuccessful) {
39
+ promise.resolve(task.result == true)
40
+ } else {
41
+ promise.reject(
42
+ "ERR_GOOGLE_PAY_READY_TO_PAY",
43
+ task.exception?.localizedMessage ?: "Google Pay readiness check failed.",
44
+ task.exception
45
+ )
46
+ }
47
+ }
48
+ }
49
+
50
+ OnActivityResult { _, (requestCode, resultCode, data) ->
51
+ PendingGooglePayPayment.handleActivityResult(requestCode, resultCode, data)
52
+ }
53
+
54
+ View(ExpoGooglePayView::class) {
55
+ Events(
56
+ "onTokenReceived",
57
+ "onError",
58
+ "onCancel",
59
+ "onReadyToPayChanged"
60
+ )
61
+
62
+ Prop("paymentRequestJson") { view: ExpoGooglePayView, paymentRequestJson: String? ->
63
+ view.paymentRequestJson = paymentRequestJson
64
+ }
65
+
66
+ Prop("isReadyToPayRequestJson") { view: ExpoGooglePayView, isReadyToPayRequestJson: String? ->
67
+ view.isReadyToPayRequestJson = isReadyToPayRequestJson
68
+ }
69
+
70
+ Prop("environment") { view: ExpoGooglePayView, environment: String? ->
71
+ view.environment = environment
72
+ }
73
+
74
+ Prop("buttonTheme") { view: ExpoGooglePayView, buttonTheme: String? ->
75
+ view.buttonTheme = buttonTheme
76
+ }
77
+
78
+ Prop("buttonType") { view: ExpoGooglePayView, buttonType: String? ->
79
+ view.buttonType = buttonType
80
+ }
81
+
82
+ Prop("cornerRadius") { view: ExpoGooglePayView, cornerRadius: Double? ->
83
+ view.cornerRadius = cornerRadius
84
+ }
85
+
86
+ Prop("disabled") { view: ExpoGooglePayView, disabled: Boolean? ->
87
+ view.disabled = disabled == true
88
+ }
89
+
90
+ Prop("existingPaymentMethodRequired") { view: ExpoGooglePayView, existingPaymentMethodRequired: Boolean? ->
91
+ view.existingPaymentMethodRequired = existingPaymentMethodRequired == true
92
+ }
93
+
94
+ Prop("autoCheckReadiness") { view: ExpoGooglePayView, autoCheckReadiness: Boolean? ->
95
+ view.autoCheckReadiness = autoCheckReadiness != false
96
+ }
97
+
98
+ Prop("hideWhenNotReady") { view: ExpoGooglePayView, hideWhenNotReady: Boolean? ->
99
+ view.hideWhenNotReady = hideWhenNotReady == true
100
+ }
101
+
102
+ AsyncFunction("presentPaymentSheet") { view: ExpoGooglePayView ->
103
+ view.presentPaymentSheet()
104
+ }
105
+
106
+ AsyncFunction("checkReadiness") { view: ExpoGooglePayView ->
107
+ view.checkReadiness()
108
+ }
109
+
110
+ OnViewDidUpdateProps { view: ExpoGooglePayView ->
111
+ view.onPropsUpdated()
112
+ }
113
+
114
+ OnViewDestroys { view: ExpoGooglePayView ->
115
+ PendingGooglePayPayment.clear(view)
116
+ }
117
+ }
118
+ }
119
+ }