@gmisoftware/react-native-pay 0.0.13 → 0.0.14
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/android/src/main/java/com/margelo/nitro/pay/GooglePayRequestBuilder.kt +8 -2
- package/ios/HybridPaymentHandler.swift +26 -3
- package/package.json +11 -1
- package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts +8 -4
- package/src/hooks/usePaymentCheckout.ts +9 -5
- package/src/plugin/__tests__/withApplePay.test.ts +1 -1
- package/src/utils/__tests__/paymentHelpers.test.ts +35 -1
- package/src/utils/paymentHelpers.ts +29 -9
|
@@ -3,11 +3,13 @@ package com.margelo.nitro.pay
|
|
|
3
3
|
import com.google.android.gms.wallet.WalletConstants
|
|
4
4
|
import org.json.JSONArray
|
|
5
5
|
import org.json.JSONObject
|
|
6
|
+
import java.util.Locale
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Builder for Google Pay API request objects
|
|
9
10
|
*/
|
|
10
11
|
object GooglePayRequestBuilder {
|
|
12
|
+
private val googlePayPriceLocale = Locale.US
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Creates an IsReadyToPay request
|
|
@@ -138,7 +140,7 @@ object GooglePayRequestBuilder {
|
|
|
138
140
|
|
|
139
141
|
return JSONObject().apply {
|
|
140
142
|
put("totalPriceStatus", PaymentConstants.TOTAL_PRICE_STATUS_FINAL)
|
|
141
|
-
put("totalPrice",
|
|
143
|
+
put("totalPrice", formatPrice(totalAmount))
|
|
142
144
|
put("totalPriceLabel", PaymentConstants.TOTAL_PRICE_LABEL_DEFAULT)
|
|
143
145
|
put("currencyCode", request.currencyCode)
|
|
144
146
|
put("countryCode", request.countryCode)
|
|
@@ -165,11 +167,15 @@ object GooglePayRequestBuilder {
|
|
|
165
167
|
else
|
|
166
168
|
PaymentConstants.PENDING_TYPE
|
|
167
169
|
)
|
|
168
|
-
put("price",
|
|
170
|
+
put("price", formatPrice(item.amount))
|
|
169
171
|
})
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
}
|
|
175
|
+
|
|
176
|
+
private fun formatPrice(amount: Double): String {
|
|
177
|
+
return String.format(googlePayPriceLocale, "%.2f", amount)
|
|
178
|
+
}
|
|
173
179
|
|
|
174
180
|
/**
|
|
175
181
|
* Creates allowed auth methods
|
|
@@ -30,7 +30,7 @@ private struct PaymentRequestBuilder {
|
|
|
30
30
|
paymentRequest.merchantIdentifier = merchantIdentifier
|
|
31
31
|
paymentRequest.countryCode = request.countryCode
|
|
32
32
|
paymentRequest.currencyCode = request.currencyCode
|
|
33
|
-
paymentRequest.paymentSummaryItems = buildPaymentItems(request
|
|
33
|
+
paymentRequest.paymentSummaryItems = buildPaymentItems(for: request)
|
|
34
34
|
paymentRequest.merchantCapabilities = buildMerchantCapabilities(request.merchantCapabilities)
|
|
35
35
|
paymentRequest.supportedNetworks = buildSupportedNetworks(request.supportedNetworks)
|
|
36
36
|
|
|
@@ -67,8 +67,8 @@ private struct PaymentRequestBuilder {
|
|
|
67
67
|
return nil
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
private static func buildPaymentItems(
|
|
71
|
-
|
|
70
|
+
private static func buildPaymentItems(for request: PaymentRequest) -> [PKPaymentSummaryItem] {
|
|
71
|
+
let lineItems = request.paymentItems.map { item in
|
|
72
72
|
let pkItem = PKPaymentSummaryItem(
|
|
73
73
|
label: item.label,
|
|
74
74
|
amount: NSDecimalNumber(decimal: Decimal(item.amount))
|
|
@@ -76,6 +76,29 @@ private struct PaymentRequestBuilder {
|
|
|
76
76
|
pkItem.type = item.type == .final ? .final : .pending
|
|
77
77
|
return pkItem
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
guard request.paymentItems.count > 1 else {
|
|
81
|
+
return lineItems
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let totalAmount = request.paymentItems.reduce(Decimal.zero) { partialResult, item in
|
|
85
|
+
partialResult + Decimal(item.amount)
|
|
86
|
+
}
|
|
87
|
+
let totalLabel: String
|
|
88
|
+
if let merchantName = request.merchantName?
|
|
89
|
+
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines),
|
|
90
|
+
!merchantName.isEmpty {
|
|
91
|
+
totalLabel = merchantName
|
|
92
|
+
} else {
|
|
93
|
+
totalLabel = "Total"
|
|
94
|
+
}
|
|
95
|
+
let totalItem = PKPaymentSummaryItem(
|
|
96
|
+
label: totalLabel,
|
|
97
|
+
amount: NSDecimalNumber(decimal: totalAmount)
|
|
98
|
+
)
|
|
99
|
+
totalItem.type = .final
|
|
100
|
+
|
|
101
|
+
return lineItems + [totalItem]
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
private static func buildMerchantCapabilities(_ capabilities: [String]) -> PKMerchantCapability {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gmisoftware/react-native-pay",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"author": "gmi.software",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
"plugins": [
|
|
44
44
|
"prettier"
|
|
45
45
|
],
|
|
46
|
+
"overrides": [
|
|
47
|
+
{
|
|
48
|
+
"files": [
|
|
49
|
+
"jest.setup.js"
|
|
50
|
+
],
|
|
51
|
+
"env": {
|
|
52
|
+
"jest": true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
],
|
|
46
56
|
"rules": {
|
|
47
57
|
"prettier/prettier": [
|
|
48
58
|
"warn",
|
|
@@ -23,10 +23,7 @@ describe('usePaymentCheckout integration', () => {
|
|
|
23
23
|
mockStartPayment.mockReset()
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
const renderCheckout = () =>
|
|
27
|
-
renderHook(() =>
|
|
28
|
-
usePaymentCheckout({})
|
|
29
|
-
)
|
|
26
|
+
const renderCheckout = () => renderHook(() => usePaymentCheckout({}))
|
|
30
27
|
|
|
31
28
|
it('loads payment service status on mount', async () => {
|
|
32
29
|
const { result } = renderCheckout()
|
|
@@ -108,6 +105,13 @@ describe('usePaymentCheckout integration', () => {
|
|
|
108
105
|
])
|
|
109
106
|
expect(result.current.paymentRequest.countryCode).toBe('US')
|
|
110
107
|
expect(result.current.paymentRequest.currencyCode).toBe('USD')
|
|
108
|
+
expect(result.current.paymentRequest).not.toHaveProperty(
|
|
109
|
+
'applePayMerchantIdentifier'
|
|
110
|
+
)
|
|
111
|
+
expect(result.current.paymentRequest).not.toHaveProperty('merchantName')
|
|
112
|
+
expect(result.current.paymentRequest).not.toHaveProperty(
|
|
113
|
+
'googlePayMerchantId'
|
|
114
|
+
)
|
|
111
115
|
})
|
|
112
116
|
|
|
113
117
|
it('rejects startPayment when cart is empty', async () => {
|
|
@@ -11,7 +11,11 @@ import type {
|
|
|
11
11
|
PayServiceStatus,
|
|
12
12
|
GooglePayEnvironment,
|
|
13
13
|
} from '../types'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createPaymentItem,
|
|
16
|
+
calculateTotal,
|
|
17
|
+
sanitizePaymentRequest,
|
|
18
|
+
} from '../utils'
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Configuration for `usePaymentCheckout`.
|
|
@@ -207,20 +211,20 @@ export function usePaymentCheckout(
|
|
|
207
211
|
googlePayGatewayMerchantId,
|
|
208
212
|
} = config
|
|
209
213
|
|
|
210
|
-
return {
|
|
211
|
-
applePayMerchantIdentifier,
|
|
214
|
+
return sanitizePaymentRequest({
|
|
212
215
|
countryCode,
|
|
213
|
-
merchantName,
|
|
214
216
|
currencyCode,
|
|
215
217
|
supportedNetworks,
|
|
216
218
|
merchantCapabilities,
|
|
217
219
|
paymentItems:
|
|
218
220
|
items.length > 0 ? items : [createPaymentItem('Total', 0, 'final')],
|
|
221
|
+
applePayMerchantIdentifier,
|
|
222
|
+
merchantName,
|
|
219
223
|
googlePayMerchantId,
|
|
220
224
|
googlePayEnvironment,
|
|
221
225
|
googlePayGateway,
|
|
222
226
|
googlePayGatewayMerchantId,
|
|
223
|
-
}
|
|
227
|
+
})
|
|
224
228
|
}, [config, items])
|
|
225
229
|
|
|
226
230
|
// Cart operations
|
|
@@ -76,7 +76,7 @@ describe('withApplePay', () => {
|
|
|
76
76
|
expect(result).toEqual({
|
|
77
77
|
modResults: {
|
|
78
78
|
'com.apple.developer.in-app-payments': ['merchant.com.test'],
|
|
79
|
-
ReactNativePayApplePayMerchantIdentifiers: ['merchant.com.test'],
|
|
79
|
+
'ReactNativePayApplePayMerchantIdentifiers': ['merchant.com.test'],
|
|
80
80
|
},
|
|
81
81
|
})
|
|
82
82
|
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
formatNetworkName,
|
|
8
8
|
isNetworkSupported,
|
|
9
9
|
parseAmount,
|
|
10
|
+
sanitizePaymentRequest,
|
|
10
11
|
} from '../paymentHelpers'
|
|
11
12
|
|
|
12
13
|
describe('paymentHelpers', () => {
|
|
@@ -71,10 +72,43 @@ describe('paymentHelpers', () => {
|
|
|
71
72
|
expect(request.supportedNetworks).toEqual(['visa'])
|
|
72
73
|
expect(request.merchantCapabilities).toEqual(['EMV'])
|
|
73
74
|
expect(request.merchantName).toBe('My Store')
|
|
74
|
-
expect(request.applePayMerchantIdentifier).toBe(
|
|
75
|
+
expect(request.applePayMerchantIdentifier).toBe(
|
|
76
|
+
'merchant.com.apple.override'
|
|
77
|
+
)
|
|
75
78
|
expect(request.googlePayMerchantId).toBe('google-pay-merchant-id')
|
|
76
79
|
})
|
|
77
80
|
|
|
81
|
+
it('omits undefined optional fields from created payment requests', () => {
|
|
82
|
+
const request = createPaymentRequest({
|
|
83
|
+
amount: 10,
|
|
84
|
+
label: 'Order',
|
|
85
|
+
merchantName: undefined,
|
|
86
|
+
googlePayMerchantId: undefined,
|
|
87
|
+
googlePayGateway: 'stripe',
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(request).not.toHaveProperty('merchantName')
|
|
91
|
+
expect(request).not.toHaveProperty('googlePayMerchantId')
|
|
92
|
+
expect(request.googlePayGateway).toBe('stripe')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('sanitizes payment requests before native bridging', () => {
|
|
96
|
+
const request = sanitizePaymentRequest({
|
|
97
|
+
countryCode: 'PL',
|
|
98
|
+
currencyCode: 'PLN',
|
|
99
|
+
paymentItems: [{ label: 'Order', amount: 10, type: 'final' }],
|
|
100
|
+
supportedNetworks: ['visa'],
|
|
101
|
+
merchantCapabilities: ['3DS'],
|
|
102
|
+
merchantName: undefined,
|
|
103
|
+
googlePayGateway: 'przelewy24',
|
|
104
|
+
googlePayGatewayMerchantId: undefined,
|
|
105
|
+
} as any)
|
|
106
|
+
|
|
107
|
+
expect(request).not.toHaveProperty('merchantName')
|
|
108
|
+
expect(request).not.toHaveProperty('googlePayGatewayMerchantId')
|
|
109
|
+
expect(request.googlePayGateway).toBe('przelewy24')
|
|
110
|
+
})
|
|
111
|
+
|
|
78
112
|
it('formats and parses amount values', () => {
|
|
79
113
|
expect(formatAmount(29.9)).toBe('29.90')
|
|
80
114
|
expect(parseAmount('29.90')).toBeCloseTo(29.9)
|
|
@@ -29,6 +29,31 @@ export const CommonNetworks = {
|
|
|
29
29
|
DISCOVER: 'discover',
|
|
30
30
|
} as const
|
|
31
31
|
|
|
32
|
+
const DEFAULT_SUPPORTED_NETWORKS: PaymentRequest['supportedNetworks'] = [
|
|
33
|
+
CommonNetworks.VISA,
|
|
34
|
+
CommonNetworks.MASTERCARD,
|
|
35
|
+
CommonNetworks.AMEX,
|
|
36
|
+
CommonNetworks.DISCOVER,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const DEFAULT_MERCHANT_CAPABILITIES: PaymentRequest['merchantCapabilities'] = [
|
|
40
|
+
'3DS',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
function omitUndefinedProperties<T extends object>(value: T): T {
|
|
44
|
+
return Object.fromEntries(
|
|
45
|
+
Object.entries(value as Record<string, unknown>).filter(
|
|
46
|
+
([, entryValue]) => entryValue !== undefined
|
|
47
|
+
)
|
|
48
|
+
) as T
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function sanitizePaymentRequest(
|
|
52
|
+
request: PaymentRequest
|
|
53
|
+
): PaymentRequest {
|
|
54
|
+
return omitUndefinedProperties(request)
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
/**
|
|
33
58
|
* Creates a payment item with the specified label, amount, and type.
|
|
34
59
|
*
|
|
@@ -103,24 +128,19 @@ export function createPaymentRequest(
|
|
|
103
128
|
label,
|
|
104
129
|
countryCode = 'US',
|
|
105
130
|
currencyCode = 'USD',
|
|
106
|
-
supportedNetworks =
|
|
107
|
-
|
|
108
|
-
CommonNetworks.MASTERCARD,
|
|
109
|
-
CommonNetworks.AMEX,
|
|
110
|
-
CommonNetworks.DISCOVER,
|
|
111
|
-
],
|
|
112
|
-
merchantCapabilities = ['3DS'],
|
|
131
|
+
supportedNetworks = DEFAULT_SUPPORTED_NETWORKS,
|
|
132
|
+
merchantCapabilities = DEFAULT_MERCHANT_CAPABILITIES,
|
|
113
133
|
...rest
|
|
114
134
|
} = options
|
|
115
135
|
|
|
116
|
-
return {
|
|
136
|
+
return sanitizePaymentRequest({
|
|
117
137
|
countryCode,
|
|
118
138
|
currencyCode,
|
|
119
139
|
paymentItems: [createPaymentItem(label, amount, 'final')],
|
|
120
140
|
supportedNetworks,
|
|
121
141
|
merchantCapabilities,
|
|
122
142
|
...rest,
|
|
123
|
-
}
|
|
143
|
+
} as PaymentRequest)
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
/**
|