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.
- package/README.md +211 -0
- package/android/build.gradle +8 -4
- package/android/src/main/AndroidManifest.xml +6 -1
- package/android/src/main/java/expo/modules/googlepay/ExpoGooglePayModule.kt +119 -0
- package/android/src/main/java/expo/modules/googlepay/ExpoGooglePayView.kt +475 -0
- package/build/ExpoPay.types.d.ts +46 -7
- package/build/ExpoPay.types.d.ts.map +1 -1
- package/build/ExpoPay.types.js.map +1 -1
- package/build/ExpoPayModule.d.ts +6 -8
- package/build/ExpoPayModule.d.ts.map +1 -1
- package/build/ExpoPayModule.js +6 -2
- package/build/ExpoPayModule.js.map +1 -1
- package/build/ExpoPayModule.web.d.ts +5 -4
- package/build/ExpoPayModule.web.d.ts.map +1 -1
- package/build/ExpoPayModule.web.js +6 -4
- package/build/ExpoPayModule.web.js.map +1 -1
- package/build/ExpoPayView.d.ts +7 -3
- package/build/ExpoPayView.d.ts.map +1 -1
- package/build/ExpoPayView.js +21 -5
- package/build/ExpoPayView.js.map +1 -1
- package/build/ExpoPayView.web.d.ts +2 -2
- package/build/ExpoPayView.web.d.ts.map +1 -1
- package/build/ExpoPayView.web.js +2 -3
- package/build/ExpoPayView.web.js.map +1 -1
- package/build/GooglePay.d.ts +5 -0
- package/build/GooglePay.d.ts.map +1 -0
- package/build/GooglePay.js +7 -0
- package/build/GooglePay.js.map +1 -0
- package/build/GooglePayJson.d.ts +3 -0
- package/build/GooglePayJson.d.ts.map +1 -0
- package/build/GooglePayJson.js +4 -0
- package/build/GooglePayJson.js.map +1 -0
- package/build/index.d.ts +3 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -5
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/package.json +37 -5
- package/src/ExpoPay.types.ts +75 -7
- package/src/ExpoPayModule.ts +20 -8
- package/src/ExpoPayModule.web.ts +14 -5
- package/src/ExpoPayView.tsx +65 -6
- package/src/ExpoPayView.web.tsx +3 -4
- package/src/GooglePay.ts +15 -0
- package/src/GooglePayJson.ts +5 -0
- package/src/index.ts +13 -5
- package/.prettierrc +0 -8
- package/android/src/main/java/expo/modules/pay/ExpoPayModule.kt +0 -29
- package/android/src/main/java/expo/modules/pay/ExpoPayView.kt +0 -26
- package/eslint.config.cjs +0 -5
- 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.
|
package/android/build.gradle
CHANGED
|
@@ -3,16 +3,20 @@ plugins {
|
|
|
3
3
|
id 'expo-module-gradle-plugin'
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
group = 'expo.modules.
|
|
7
|
-
version = '0.1.
|
|
6
|
+
group = 'expo.modules.googlepay'
|
|
7
|
+
version = '0.1.1'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
|
-
namespace "expo.modules.
|
|
10
|
+
namespace "expo.modules.googlepay"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName "0.1.
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|