diff --git a/CHANGELOG.md b/CHANGELOG.md index 828282e..3ee86bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,13 @@ All notable changes to this project are documented in this file. Pay prices for zero-decimal currencies validated by `normalizePaymentRequest` are sent without decimals (`"1500"` instead of `"1500.00"`), and Android rejects a negative total or a third decimal place instead of rounding. +- **iOS: the Apple Pay sheet now names the merchant in the total.** PassKit shows the last summary + item as the total ("Pay *label*"). A one-item cart sent that item as the total, so the sheet read + "Pay Coffee"; a cart with several items and no `merchantName` read "Pay Total". The library now + always appends a total labelled with `merchantName`, and falls back to the app's display name + (`CFBundleDisplayName`, then `CFBundleName`). A one-item cart now shows the item line and then + "Pay *merchant*". If you passed your own "Total" item, remove it: it now appears as a line item + above the total. - **Installing the package no longer runs `tsc` in your project.** 0.0.17 and earlier shipped a `postinstall` script (`tsc || exit 0`). The package does not include its own `tsconfig.json`, so `tsc` picked up the app's `tsconfig.json` and type-checked the whole app on every install; with a diff --git a/README.md b/README.md index 6fba0f9..408ae66 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ function CheckoutScreen() { isProcessing, error, } = usePaymentCheckout({ + merchantName: 'Your Store', currencyCode: 'USD', countryCode: 'US', }) @@ -360,7 +361,7 @@ The all-in-one hook for handling payments. Manages availability checking, cart s ```typescript interface UsePaymentCheckoutConfig { - merchantName?: string; + merchantName?: string; // "Pay " on Apple Pay; defaults to the app name countryCode?: string; // Default: 'US' currencyCode?: string; // Default: 'USD' supportedNetworks?: string[]; // Default: ['visa', 'mastercard', 'amex', 'discover'] @@ -410,6 +411,7 @@ interface UsePaymentCheckoutReturn { ```typescript const checkout = usePaymentCheckout({ + merchantName: "Your Store", currencyCode: "USD", countryCode: "US", googlePayMerchantId: "your_google_pay_merchant_id", @@ -467,16 +469,19 @@ Start a payment request. const result = await HybridPaymentHandler.startPayment( // Validates amounts before the sheet opens; see normalizePaymentRequest normalizePaymentRequest({ + merchantName: "Your Store", countryCode: "US", currencyCode: "USD", merchantCapabilities: ["3DS"], supportedNetworks: ["visa", "mastercard", "amex"], - paymentItems: [{ label: "Total", amount: "29.99", type: "final" }], + paymentItems: [{ label: "Coffee Subscription", amount: "29.99", type: "final" }], googlePayMerchantId: "your_google_pay_merchant_id", }) ); ``` +On iOS, the Apple Pay sheet lists your `paymentItems` and then a total labelled with `merchantName` ("Pay Your Store"). The library adds that total for you, so don't add your own "Total" item. + **Options:** ```typescript @@ -614,6 +619,7 @@ import { createPaymentRequest } from "@gmisoftware/react-native-pay"; const request = createPaymentRequest({ amount: 29.99, label: "Coffee Subscription", + merchantName: "Your Store", countryCode: "US", currencyCode: "USD", googlePayMerchantId: "your_google_pay_merchant_id", @@ -695,7 +701,7 @@ interface PaymentItem { // Payment Request interface PaymentRequest { applePayMerchantIdentifier?: string; - merchantName?: string; + merchantName?: string; // "Pay " on Apple Pay; defaults to the app name countryCode: string; currencyCode: string; paymentItems: PaymentItem[]; @@ -829,6 +835,7 @@ if (!status.canMakePayments) { const paymentRequest = createPaymentRequest({ amount: 99.99, label: 'Premium Subscription', + merchantName: 'Your Store', countryCode: 'US', currencyCode: 'USD', googlePayMerchantId: 'your_google_pay_merchant_id', @@ -873,6 +880,7 @@ return Platform.OS === 'ios' ? ( ```typescript const checkout = usePaymentCheckout({ + merchantName: "Your Store", currencyCode: "USD", }); diff --git a/docs/docs/api/hybrid-payment-handler.md b/docs/docs/api/hybrid-payment-handler.md index 96b1a41..bc92d40 100644 --- a/docs/docs/api/hybrid-payment-handler.md +++ b/docs/docs/api/hybrid-payment-handler.md @@ -35,11 +35,12 @@ import { HybridPaymentHandler, normalizePaymentRequest } from '@gmisoftware/reac const result = await HybridPaymentHandler.startPayment( normalizePaymentRequest({ + merchantName: 'Your Store', countryCode: 'US', currencyCode: 'USD', merchantCapabilities: ['3DS'], supportedNetworks: ['visa', 'mastercard', 'amex'], - paymentItems: [{ label: 'Total', amount: '29.99', type: 'final' }], + paymentItems: [{ label: 'Coffee Subscription', amount: '29.99', type: 'final' }], googlePayMerchantId: 'your_google_pay_merchant_id', }) ) @@ -74,6 +75,8 @@ See [Charge before the sheet closes](/docs/guides/server-processing#charge-befor On iOS, the merchant ID is resolved from the app entitlements by default. Use `applePayMerchantIdentifier` only when you need to override the configured value at runtime. +On iOS, the Apple Pay sheet lists your `paymentItems` and then a total labelled with `merchantName` ("Pay Your Store"). The library adds that total for you, so don't add your own "Total" item. Without `merchantName`, the total uses your app's display name. + Build the request yourself or use [createPaymentRequest](/docs/api/utils#createpaymentrequestoptions) for a single line item with defaults. Only one payment can be in flight at a time. A call made while another payment is still in progress, including one started by `usePaymentCheckout`, resolves immediately with `{ success: false, error: 'A payment is already in progress' }` and does not affect the payment in flight. This applies on iOS and Android. With `onAuthorize`, the payment stays in flight until your callback settles. diff --git a/docs/docs/api/types.md b/docs/docs/api/types.md index 8429e1c..54fb124 100644 --- a/docs/docs/api/types.md +++ b/docs/docs/api/types.md @@ -71,7 +71,7 @@ interface PaymentRequest { On iOS, `applePayMerchantIdentifier` is optional and only needed when you want to override the Merchant ID configured in the native app entitlements. -`merchantName` is the name shown on the Google Pay sheet and the label of the Apple Pay total (when the cart has more than one item). If it is missing or blank, Android uses the app's label from the manifest; iOS labels the total "Total". +`merchantName` is the business name shown to the payer: the name on the Google Pay sheet and the label of the Apple Pay total. Apple Pay shows the last summary item as the total ("Pay *label*"), so on iOS the library appends a total item labelled with `merchantName` after your `paymentItems`, also when there is only one item. Don't add your own "Total" item. If `merchantName` is missing or blank, Android uses the app's label from the manifest and iOS uses the app's display name (`CFBundleDisplayName`, then `CFBundleName`). --- diff --git a/docs/docs/api/use-payment-checkout.md b/docs/docs/api/use-payment-checkout.md index 3580f23..c9709b4 100644 --- a/docs/docs/api/use-payment-checkout.md +++ b/docs/docs/api/use-payment-checkout.md @@ -16,7 +16,7 @@ Source of truth for these types: [`package/src/hooks/usePaymentCheckout.ts`](htt ```ts interface UsePaymentCheckoutConfig { - merchantName?: string + merchantName?: string // business name shown on the sheet countryCode?: string // default: 'US' currencyCode?: string // default: 'USD' supportedNetworks?: string[] // default: ['visa', 'mastercard', 'amex', 'discover'] @@ -32,6 +32,7 @@ interface UsePaymentCheckoutConfig { } ``` +- **merchantName** — The business name shown to the payer. On iOS, Apple Pay labels the total with it, so the sheet reads "Pay *merchantName*". On Android, it is the merchant name on the Google Pay sheet. Without it, iOS uses your app's display name (`CFBundleDisplayName`) and Android uses the app's label. Set it on both platforms. - **onAuthorize** — Called with the token after the user authorizes the payment. Charge it on your server and resolve with `{ status: 'success' }` or `{ status: 'failure', error }`. See [Charge before the sheet closes](/docs/guides/server-processing#charge-before-the-sheet-closes). ## Return value @@ -94,6 +95,7 @@ const { error, reset, } = usePaymentCheckout({ + merchantName: 'Your Store', currencyCode: 'USD', countryCode: 'US', googlePayMerchantId: 'your_google_pay_merchant_id', diff --git a/docs/docs/api/utils.md b/docs/docs/api/utils.md index c1323a9..629ddb5 100644 --- a/docs/docs/api/utils.md +++ b/docs/docs/api/utils.md @@ -24,6 +24,7 @@ import { createPaymentRequest } from '@gmisoftware/react-native-pay' const request = createPaymentRequest({ amount: 29.99, // or '29.99' label: 'Coffee Subscription', + merchantName: 'Your Store', countryCode: 'US', currencyCode: 'USD', // Optional: applePayMerchantIdentifier, googlePayMerchantId, diff --git a/docs/docs/examples.md b/docs/docs/examples.md index 4f5d390..2453fdf 100644 --- a/docs/docs/examples.md +++ b/docs/docs/examples.md @@ -26,6 +26,7 @@ const { isProcessing, reset, } = usePaymentCheckout({ + merchantName: 'Your Store', countryCode: 'US', currencyCode: 'USD', }) diff --git a/docs/docs/guides/dynamic-cart.md b/docs/docs/guides/dynamic-cart.md index cba7025..3abceae 100644 --- a/docs/docs/guides/dynamic-cart.md +++ b/docs/docs/guides/dynamic-cart.md @@ -66,5 +66,5 @@ const handleClearCart = () => { - **total** is derived from `items`; you don’t set it manually. It is an exact decimal sum. - **Round computed amounts** with `formatAmount(value, currencyCode)`. `startPayment()` rejects amounts with more decimal places than the currency allows, which includes unrounded float results such as `1.1 * 3`. See [`PaymentAmount`](/docs/api/types#paymentamount). - **Discounts** can be negative line items, as long as the total is not negative. -- Use **type: 'final'** for confirmed amounts (e.g. tax, total); use **'pending'** for estimates (e.g. shipping) if your flow supports it. +- Use **type: 'final'** for confirmed amounts (e.g. product price, tax); use **'pending'** for estimates (e.g. shipping) if your flow supports it. - **One pending item makes the total an estimate.** Google Pay shows the total as estimated (`totalPriceStatus: 'ESTIMATED'`) and Apple Pay marks the total as pending. An estimate does not let you charge a different amount: Google Pay requires the amount you authorize and charge to match the `totalPrice` shown on the sheet, and a mismatch can be declined. If the final amount differs from the estimate, start a new payment with the final amount, unless your gateway explicitly supports adjusting it (for example, incremental authorization). diff --git a/docs/docs/guides/manual-flow.md b/docs/docs/guides/manual-flow.md index 55593a7..649fd4b 100644 --- a/docs/docs/guides/manual-flow.md +++ b/docs/docs/guides/manual-flow.md @@ -24,6 +24,7 @@ import { createPaymentRequest, HybridPaymentHandler } from '@gmisoftware/react-n const paymentRequest = createPaymentRequest({ amount: 99.99, label: 'Premium Subscription', + merchantName: 'Your Store', countryCode: 'US', currencyCode: 'USD', googlePayMerchantId: 'your_google_pay_merchant_id', diff --git a/docs/docs/integrations/przelewy24.md b/docs/docs/integrations/przelewy24.md index 015a966..06e9379 100644 --- a/docs/docs/integrations/przelewy24.md +++ b/docs/docs/integrations/przelewy24.md @@ -68,6 +68,7 @@ import { export function CheckoutWithPrzelewy24() { const {startPayment, canMakePayments, isProcessing} = usePaymentCheckout({ + merchantName: 'Your Store', countryCode: 'PL', currencyCode: 'PLN', googlePayMerchantId: 'your_google_pay_merchant_id', diff --git a/docs/docs/quick-start.md b/docs/docs/quick-start.md index 25b460a..4e944b2 100644 --- a/docs/docs/quick-start.md +++ b/docs/docs/quick-start.md @@ -67,6 +67,7 @@ function CheckoutScreen() { isProcessing, error, } = usePaymentCheckout({ + merchantName: 'Your Store', currencyCode: 'USD', countryCode: 'US', }) diff --git a/docs/docs/setup/ios-apple-pay.md b/docs/docs/setup/ios-apple-pay.md index 96480c5..2165c29 100644 --- a/docs/docs/setup/ios-apple-pay.md +++ b/docs/docs/setup/ios-apple-pay.md @@ -40,7 +40,21 @@ The runtime API reads this Merchant ID from the native app entitlements automati If you are integrating with Przelewy24, see [Przelewy24](/docs/integrations/przelewy24) for an end-to-end app + backend flow. -## 3. Prebuild and run +## 3. Name your business on the sheet + +Apple Pay shows the total as "Pay *merchant*". Pass your business name as `merchantName`: + +```ts +usePaymentCheckout({ + merchantName: 'Your Store', + currencyCode: 'USD', + countryCode: 'US', +}) +``` + +The sheet lists your cart items and then the total line "Pay Your Store", which the library adds for you. If you omit `merchantName`, the total uses your app's display name (`CFBundleDisplayName`). + +## 4. Prebuild and run ```bash npx expo prebuild --clean diff --git a/example/app/(tabs)/index.tsx b/example/app/(tabs)/index.tsx index b88dc05..68dd99b 100644 --- a/example/app/(tabs)/index.tsx +++ b/example/app/(tabs)/index.tsx @@ -41,6 +41,7 @@ export default function TabOneScreen() { error, reset, } = usePaymentCheckout({ + merchantName: "React Native Pay Demo", countryCode: "US", currencyCode: "USD", // Stands in for your backend charging the token while the sheet waits. diff --git a/package/ios/HybridPaymentHandler.swift b/package/ios/HybridPaymentHandler.swift index 02a13dc..35e676f 100644 --- a/package/ios/HybridPaymentHandler.swift +++ b/package/ios/HybridPaymentHandler.swift @@ -34,6 +34,8 @@ private typealias AuthorizeCallback = (PaymentToken) -> Promise 1 else { - return lineItems - } - - let totalLabel: String - if let merchantName = request.merchantName? - .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines), - !merchantName.isEmpty { - totalLabel = merchantName - } else { - totalLabel = "Total" - } + // PassKit shows the last item as the grand total ("Pay