Skip to content

Commit afafbb5

Browse files
refactor: enhance payment request handling and improve formatting
- Updated payment request construction to omit undefined properties, ensuring cleaner requests. - Refactored payment item building logic to improve clarity and maintainability. - Standardized price formatting in Google Pay request builder. - Improved test coverage for payment request sanitization and validation. - Adjusted various components to ensure consistent handling of payment request properties.
1 parent 3062ea7 commit afafbb5

7 files changed

Lines changed: 115 additions & 23 deletions

File tree

example/app/(tabs)/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ export default function TabOneScreen() {
4343
if (canMakePayments) {
4444
Alert.alert(
4545
`${paymentServiceName} Available`,
46-
`${paymentServiceName} is ready to use!`
46+
`${paymentServiceName} is ready to use!`,
4747
);
4848
} else if (canSetupCards) {
4949
Alert.alert(
5050
"Setup Required",
51-
`Please set up ${paymentServiceName} in Settings`
51+
`Please set up ${paymentServiceName} in Settings`,
5252
);
5353
} else {
5454
Alert.alert(
5555
"Not Available",
56-
`${paymentServiceName} is not available on this device`
56+
`${paymentServiceName} is not available on this device`,
5757
);
5858
}
5959
};
@@ -98,7 +98,7 @@ export default function TabOneScreen() {
9898
clearItems();
9999
},
100100
},
101-
]
101+
],
102102
);
103103
} else if (paymentResult?.error) {
104104
Alert.alert("Payment Failed", paymentResult.error, [{ text: "OK" }]);

package/android/src/main/java/com/margelo/nitro/pay/GooglePayRequestBuilder.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package com.margelo.nitro.pay
33
import com.google.android.gms.wallet.WalletConstants
44
import org.json.JSONArray
55
import org.json.JSONObject
6+
import java.util.Locale
67

78
/**
89
* Builder for Google Pay API request objects
910
*/
1011
object GooglePayRequestBuilder {
12+
private val googlePayPriceLocale = Locale.US
1113

1214
/**
1315
* Creates an IsReadyToPay request
@@ -138,7 +140,7 @@ object GooglePayRequestBuilder {
138140

139141
return JSONObject().apply {
140142
put("totalPriceStatus", PaymentConstants.TOTAL_PRICE_STATUS_FINAL)
141-
put("totalPrice", String.format("%.2f", totalAmount))
143+
put("totalPrice", formatPrice(totalAmount))
142144
put("totalPriceLabel", PaymentConstants.TOTAL_PRICE_LABEL_DEFAULT)
143145
put("currencyCode", request.currencyCode)
144146
put("countryCode", request.countryCode)
@@ -165,11 +167,15 @@ object GooglePayRequestBuilder {
165167
else
166168
PaymentConstants.PENDING_TYPE
167169
)
168-
put("price", String.format("%.2f", item.amount))
170+
put("price", formatPrice(item.amount))
169171
})
170172
}
171173
}
172174
}
175+
176+
private fun formatPrice(amount: Double): String {
177+
return String.format(googlePayPriceLocale, "%.2f", amount)
178+
}
173179

174180
/**
175181
* Creates allowed auth methods

package/ios/HybridPaymentHandler.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ private struct PaymentRequestBuilder {
3030
paymentRequest.merchantIdentifier = merchantIdentifier
3131
paymentRequest.countryCode = request.countryCode
3232
paymentRequest.currencyCode = request.currencyCode
33-
paymentRequest.paymentSummaryItems = buildPaymentItems(request.paymentItems)
33+
paymentRequest.paymentSummaryItems = buildPaymentItems(for: request)
3434
paymentRequest.merchantCapabilities = buildMerchantCapabilities(request.merchantCapabilities)
3535
paymentRequest.supportedNetworks = buildSupportedNetworks(request.supportedNetworks)
3636

@@ -67,15 +67,38 @@ private struct PaymentRequestBuilder {
6767
return nil
6868
}
6969

70-
private static func buildPaymentItems(_ items: [PaymentItem]) -> [PKPaymentSummaryItem] {
71-
return items.map { item in
70+
private static func buildPaymentItems(for request: PaymentRequest) -> [PKPaymentSummaryItem] {
71+
let lineItems = request.paymentItems.map { item in
7272
let pkItem = PKPaymentSummaryItem(
7373
label: item.label,
7474
amount: NSDecimalNumber(decimal: Decimal(item.amount))
7575
)
7676
pkItem.type = item.type == .final ? .final : .pending
7777
return pkItem
7878
}
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]
79102
}
80103

81104
private static func buildMerchantCapabilities(_ capabilities: [String]) -> PKMerchantCapability {

package/src/hooks/__tests__/usePaymentCheckout.integration.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ describe('usePaymentCheckout integration', () => {
105105
])
106106
expect(result.current.paymentRequest.countryCode).toBe('US')
107107
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+
)
108115
})
109116

110117
it('rejects startPayment when cart is empty', async () => {

package/src/hooks/usePaymentCheckout.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import type {
1111
PayServiceStatus,
1212
GooglePayEnvironment,
1313
} from '../types'
14-
import { createPaymentItem, calculateTotal } from '../utils'
14+
import {
15+
createPaymentItem,
16+
calculateTotal,
17+
sanitizePaymentRequest,
18+
} from '../utils'
1519

1620
/**
1721
* Configuration for `usePaymentCheckout`.
@@ -207,20 +211,20 @@ export function usePaymentCheckout(
207211
googlePayGatewayMerchantId,
208212
} = config
209213

210-
return {
211-
applePayMerchantIdentifier,
214+
return sanitizePaymentRequest({
212215
countryCode,
213-
merchantName,
214216
currencyCode,
215217
supportedNetworks,
216218
merchantCapabilities,
217219
paymentItems:
218220
items.length > 0 ? items : [createPaymentItem('Total', 0, 'final')],
221+
applePayMerchantIdentifier,
222+
merchantName,
219223
googlePayMerchantId,
220224
googlePayEnvironment,
221225
googlePayGateway,
222226
googlePayGatewayMerchantId,
223-
}
227+
})
224228
}, [config, items])
225229

226230
// Cart operations

package/src/utils/__tests__/paymentHelpers.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
formatNetworkName,
88
isNetworkSupported,
99
parseAmount,
10+
sanitizePaymentRequest,
1011
} from '../paymentHelpers'
1112

1213
describe('paymentHelpers', () => {
@@ -77,6 +78,37 @@ describe('paymentHelpers', () => {
7778
expect(request.googlePayMerchantId).toBe('google-pay-merchant-id')
7879
})
7980

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+
80112
it('formats and parses amount values', () => {
81113
expect(formatAmount(29.9)).toBe('29.90')
82114
expect(parseAmount('29.90')).toBeCloseTo(29.9)

package/src/utils/paymentHelpers.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ export const CommonNetworks = {
2929
DISCOVER: 'discover',
3030
} as const
3131

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+
3257
/**
3358
* Creates a payment item with the specified label, amount, and type.
3459
*
@@ -103,24 +128,19 @@ export function createPaymentRequest(
103128
label,
104129
countryCode = 'US',
105130
currencyCode = 'USD',
106-
supportedNetworks = [
107-
CommonNetworks.VISA,
108-
CommonNetworks.MASTERCARD,
109-
CommonNetworks.AMEX,
110-
CommonNetworks.DISCOVER,
111-
],
112-
merchantCapabilities = ['3DS'],
131+
supportedNetworks = DEFAULT_SUPPORTED_NETWORKS,
132+
merchantCapabilities = DEFAULT_MERCHANT_CAPABILITIES,
113133
...rest
114134
} = options
115135

116-
return {
136+
return sanitizePaymentRequest({
117137
countryCode,
118138
currencyCode,
119139
paymentItems: [createPaymentItem(label, amount, 'final')],
120140
supportedNetworks,
121141
merchantCapabilities,
122142
...rest,
123-
}
143+
} as PaymentRequest)
124144
}
125145

126146
/**

0 commit comments

Comments
 (0)