From 21c17b28e7debbbca7693e4bb18197510ef2037c Mon Sep 17 00:00:00 2001 From: Artem Chubaryan Date: Mon, 28 Sep 2026 15:52:40 -0500 Subject: [PATCH] MPSDK: Add integrator-facing `AGENTS.md` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrators increasingly build against this SDK with coding agents. This repo already had a `CLAUDE.md`, but it was written for contributors to the wrapper — yarn commands, bob build, the bridge architecture, mapper files. None of that helps someone consuming the package from npm, which is who almost everyone pointing an agent at this repo actually is. Ironically, that contributor file held exactly the tribal knowledge integrators burn iterations rediscovering, buried in a "Platform Constraints" bullet list: Proguard must be disabled, and React Native 0.75.x needs a `patch-package` workaround for Kotlin 2.2.x. Those are now first-class sections with the reasoning and the failure mode spelled out. ## The file swap - `CLAUDE.md` -> `CONTRIBUTING_AGENTS.md`, contents preserved, with a new header naming its audience and pointing integrators at `AGENTS.md`. It sits next to `CONTRIBUTING.md`, which covers the same audience for humans - New `AGENTS.md` for integrators - `CLAUDE.md` is now a symlink to `AGENTS.md`, so an agent that only knows that filename lands on the integrator doc by default — the right guess for the common case, with the contributor doc one link away DELETING the contributor content was never on the table; it is the only written record of how the bridge fits together. ## Contents All verified against this repo or the published packages: - npm install, current version (`2026.8.1`), and the native SDKs it wraps (iOS `2.6.0` / Android `2.6.1`) - Android `minSdk` 28 / `compileSdk` 36, AGP 8.9.1+ / Gradle 8.13+, Kotlin 2.2.21 - Proguard must be off. The debug build works, the release build compiles, and the SDK breaks at runtime once R8 has stripped bytecode it loads reflectively - The Kotlin 2.2.x `patch-package` workaround, with the two error strings it fixes and a link to `docs/KOTLIN_COMPATIBILITY.md` - iOS deployment target 16, and the setup Run Script phase that has to sit after `[CP] Embed Pods Frameworks` - Not compatible with Expo Go - Which files hold the sandbox credentials, and that the placeholders stay placeholders. Initialization is NATIVE and per-platform; only `authorize()` is JS - Required permissions on both platforms - The ordering rule: initialize -> request permissions -> `authorize()`. Any manager call before authorization rejects with `NOT_AUTHORIZED` - Mock reader testing, including that the wrapper exposes only `showMockReaderUI()` / `hideMockReaderUI()` because that is all the native frameworks expose — there is NO programmatic way to simulate a card tap, so an agent must ask a human to drive the floating button rather than hang waiting on a payment promise - Links to the per-page docs markdown rather than the HTML pages, which are iframe shells that return only nav to a fetch Co-Authored-By: Claude Opus 5 --- AGENTS.md | 246 +++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 64 +---------- CONTRIBUTING_AGENTS.md | 68 ++++++++++++ 3 files changed, 315 insertions(+), 63 deletions(-) create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md create mode 100644 CONTRIBUTING_AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..96dd37c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,246 @@ +# AGENTS.md — Square Mobile Payments SDK for React Native + +Guidance for coding agents **integrating the Mobile Payments SDK into a React Native app**. +This repo is the `mobile-payments-sdk-react-native` wrapper plus the Donut Counter sample in `example/`. + +Working on the wrapper itself rather than integrating it? See [CONTRIBUTING_AGENTS.md](CONTRIBUTING_AGENTS.md). + +Current package version: **2026.8.1**, wrapping native SDK **iOS 2.6.0** / **Android 2.6.1**. + +## Install + +```sh +npm install mobile-payments-sdk-react-native +# or +yarn add mobile-payments-sdk-react-native +``` + +This package contains native code. **It does not work in Expo Go** — you need a development build or a bare workflow. Both the old (bridge) and new (TurboModule) React Native architectures are supported. + +## Build constraints + +These silently break integrations. Check them before writing any code. + +### Android + +| Constraint | Value | +| :--- | :--- | +| `minSdkVersion` | 28 | +| `compileSdkVersion` / `targetSdkVersion` | 36 | +| Android Gradle Plugin | 8.9.1 or later | +| Gradle | 8.13 or later | +| Kotlin | 2.2.21 | + +In `android/build.gradle`: set those values in `ext { … }` along with `squareSdkVersion = "2.6.1"`, and add Square's Maven repo (it is not on Maven Central) to `allprojects.repositories`: + +```gradle +maven { url 'https://sdk.squareup.com/public/android/' } +``` + +Then in `android/app/build.gradle`: + +```gradle +implementation("com.squareup.sdk:mobile-payments-sdk:$squareSdkVersion") +``` + +**Proguard / R8 is not supported.** Shrinking strips bytecode the SDK loads reflectively at runtime. In `android/app/build.gradle`: + +```gradle +android { + buildTypes { + release { + minifyEnabled false + shrinkResources false + } + } +} +``` + +This is the most expensive thing to get wrong: the debug build works, the release build compiles, and the failure only appears at runtime. Do not "fix" it by adding keep rules. + +The AGP/Gradle floor comes from `androidx.core:core:1.18.0`, which native SDK 2.6.x depends on. Older values fail during dependency resolution, not at runtime. + +### Android + Kotlin 2.2.x — patch required on React Native 0.75.x + +Native SDK 2.6.0 requires Kotlin 2.2.21. React Native's Gradle plugin (0.75.x and earlier) references `KotlinTopLevelExtension`, which Kotlin 2.2.x removed, and compiles itself with `allWarningsAsErrors = true`, which Gradle 8.13+ deprecation warnings then trip. Both make the Android build fail during Gradle configuration: + +``` +Unresolved reference: KotlinTopLevelExtension +e: warnings found and -Werror specified +``` + +The fix is a `patch-package` patch against `@react-native/gradle-plugin`. Full steps and the patch diff: **[docs/KOTLIN_COMPATIBILITY.md](docs/KOTLIN_COMPATIBILITY.md)**. In short — install `patch-package`, add a `postinstall` script, and create `patches/@react-native+gradle-plugin+.patch` (the filename must match the installed `react-native` version exactly). + +This is a temporary workaround; it can be removed once React Native ships Kotlin 2.2.x support. + +### iOS + +Minimum deployment target **iOS 16**. Run `pod install` in `ios/`. + +**The setup run script is mandatory.** On the app target's **Build Phases** tab, add a **New Run Script Phase**, positioned *after* any `[CP] Embed Pods Frameworks` or `Embed Frameworks` phase: + +```sh +SETUP_SCRIPT=${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"/SquareMobilePaymentsSDK.framework/setup" +if [ -f "$SETUP_SCRIPT" ]; then + "$SETUP_SCRIPT" +fi +``` + +Without it the framework is not usable at runtime, and nothing fails at build time. + +## Credentials + +Three values, from the [Developer Console](https://developer.squareup.com/apps). Toggle **Sandbox** at the top of the Credentials page for test credentials. + +| Value | Where it is used | +| :--- | :--- | +| Application ID | native initialization, per platform (below) | +| Access token | `authorize(accessToken, locationId)` from JS | +| Location ID | same call — from the **Locations** page | + +Initialization is **native**, not JS. The application ID must be passed on each platform: + +- **Android** — `MobilePaymentsSdk.initialize(applicationId, this)` in `MainApplication.onCreate()`. +- **iOS** — `[SQMPMobilePaymentsSDK initializeWithApplicationLaunchOptions:launchOptions squareApplicationID:@"…"]` in `AppDelegate`. + +Authorization is JS-side and takes the access token and location ID. + +In this sample: + +| File | Holds | +| :--- | :--- | +| [example/android/app/app.properties](example/android/app/app.properties) | `APP_ID`, `LOCATION_ID`, `ACCESS_TOKEN`, surfaced as `BuildConfig` fields | +| [example-expo/app.json](example-expo/app.json) | the same three, passed through `app.plugin.js` | + +The placeholders are `INSERT APP_ID HERE`, `INSERT LOCATION_ID HERE`, `INSERT ACCESS TOKEN HERE`. **Leave them in place** — never commit real credential values to this repo or to the user's. Tell the user to fill them in locally; `app.properties` is gitignored in a fresh checkout for exactly this reason. + +A personal access token is acceptable for Sandbox only. Production authorization must use OAuth, and a shipped app must not embed a personal access token. + +## Device permissions + +The SDK does not request permissions for you. The sample uses `react-native-permissions`. + +**Android** — declare in `AndroidManifest.xml` and request at runtime: + +| Permission | Purpose | +| :--- | :--- | +| `ACCESS_FINE_LOCATION` | Confirm payments occur in a supported Square location | +| `BLUETOOTH_CONNECT` | Communicate with contactless and chip readers | +| `BLUETOOTH_SCAN` | Discover nearby readers | +| `RECORD_AUDIO` | Receive data from magstripe readers | +| `READ_PHONE_STATE` | Identify the device to Square servers | + +**iOS** — `Info.plist` keys: + +| Key | Purpose | +| :--- | :--- | +| `NSBluetoothAlwaysUsageDescription` | Connect and communicate with Square readers | +| `NSLocationWhenInUseUsageDescription` | Confirm where transactions take place | +| `NSMicrophoneUsageDescription` | Receive payment card data from magstripe readers | + +`READ_PHONE_STATE` has no iOS equivalent; the sample treats that row as already satisfied on iOS. See [example/src/Screens/PermissionsScreen.tsx](example/src/Screens/PermissionsScreen.tsx). + +## Ordering rule + +The order is not optional: + +1. **Initialize** — natively, at app launch (Android `MainApplication`, iOS `AppDelegate`). +2. **Request permissions** — at runtime, before authorizing. +3. **Authorize** — `await authorize(accessToken, locationId)`. +4. Only then call payment, reader, or settings APIs. + +Any manager call made before authorization completes rejects with **`NOT_AUTHORIZED`**. It appears in `PaymentError`, `ReaderPairingError`, and `ReaderCardInfoError`. If you see that code, the fix is ordering, not parameters. + +```ts +import { + authorize, + getAuthorizationState, + AuthorizationState, +} from 'mobile-payments-sdk-react-native'; + +try { + await authorize(accessToken, locationId); +} catch (e) { + console.log('Authorization error:', e); +} +``` + +`getAuthorizationState()` reports the current state, and `observeAuthorizationChanges()` / `stopObservingAuthorizationChanges()` subscribe to the `AuthorizationStatusChange` event. `deauthorize()` tears it down. + +## Taking a payment + +Everything is exported from the package root — `src/index.tsx` re-exports the four managers, the models, and the error enums. + +```ts +import { + startPayment, + CurrencyCode, + ProcessingMode, + PromptMode, + AdditionalPaymentMethodType, +} from 'mobile-payments-sdk-react-native'; + +const payment = await startPayment( + { + amountMoney: { amount: 100, currencyCode: CurrencyCode.USD }, + allowCardSurcharge: false, + paymentAttemptId: orderDerivedId, + processingMode: ProcessingMode.ONLINE_ONLY, + }, + { + additionalMethods: [AdditionalPaymentMethodType.ALL], + mode: PromptMode.DEFAULT, + } +); +``` + +`ProcessingMode.ONLINE_ONLY` is what Sandbox supports; `AUTO_DETECT` is for production. `cancelPayment()` aborts an in-flight payment. + +`paymentAttemptId` must be derived from an order/sale identifier in a real integration, not a fresh UUID per tap — that is what protects against duplicate payments on retry. See [example/src/Screens/HomeScreen.tsx](example/src/Screens/HomeScreen.tsx) for a complete flow, and [docs/REFERENCE.md](docs/REFERENCE.md) for the full type and method reference. + +## Testing with mock readers in Sandbox + +Physical Square readers do **not** work in Sandbox. Virtual readers come from the mock reader UI. + +```ts +import { showMockReaderUI, hideMockReaderUI } from 'mobile-payments-sdk-react-native'; + +await showMockReaderUI(); +// … +hideMockReaderUI(); +``` + +These reject outside Sandbox — wrap them in `try`/`catch`. + +**Known limitation — read this before planning an automated test.** The wrapper exposes only `showMockReaderUI()` and `hideMockReaderUI()`, because that is all the underlying native frameworks expose. There is no API to add a mock reader, select a card brand, or simulate a tap/insert/swipe. Those steps happen only through the floating button the SDK draws over your app, and require a human: + +> tap the floater → add a magstripe or contactless & chip reader → start the payment → tap the floater → tap/insert/swipe a card + +So an agent **cannot** drive an end-to-end Sandbox payment on its own. If a task requires one, say so and ask the user to perform the taps — do not sit waiting on a payment promise that will never resolve. + +Also: after testing an inserted card, remove it through the mock reader UI before starting the next payment. + +## Documentation + +Fetch the `.md` variants. The HTML pages are iframe shells and return only navigation chrome to a programmatic fetch. + +- Overview — https://developer.squareup.com/docs/mobile-payments-sdk.md +- Build with React Native — https://developer.squareup.com/docs/mobile-payments-sdk/react-native.md +- Build on Android (native constraints) — https://developer.squareup.com/docs/mobile-payments-sdk/android.md +- Build on iOS (native constraints) — https://developer.squareup.com/docs/mobile-payments-sdk/ios.md +- Handling errors — https://developer.squareup.com/docs/mobile-payments-sdk/android/handling-errors.md and https://developer.squareup.com/docs/mobile-payments-sdk/ios/handling-errors.md + +In-repo: [docs/README.md](docs/README.md) is the step-by-step setup guide, [docs/REFERENCE.md](docs/REFERENCE.md) is the type and method reference, [docs/KOTLIN_COMPATIBILITY.md](docs/KOTLIN_COMPATIBILITY.md) is the Kotlin 2.2.x patch, and [CHANGELOG.md](CHANGELOG.md) records breaking changes per release. + +## Repo layout + +``` +src/ published TypeScript API + index.tsx re-exports everything + managers/ auth, payment, reader, settings + models/ enums, objects, errors +docs/ setup guide, API reference, Kotlin patch +example/ Donut Counter sample (bare React Native 0.75) + android/app/app.properties credential placeholders +example-expo/ the same sample as an Expo development build +``` diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 67c32f6..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,63 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -React Native plugin wrapping Square's Mobile Payments SDK for in-person payments on iOS and Android. Monorepo with the library at root and a "Donut Counter" example app in `example/`. - -## Common Commands - -```bash -yarn # Install all dependencies (must use yarn, not npm) -yarn prepare # Build library (bob build) → outputs to lib/ -yarn test # Run Jest tests -yarn test --testPathPattern= # Run a single test file -yarn typecheck # TypeScript type checking -yarn lint # ESLint + Prettier -yarn lint --fix # Auto-fix lint issues - -# Example app -yarn example start # Start Metro bundler -yarn example ios # Build & run on iOS -yarn example android # Build & run on Android - -# CI-style cached builds -yarn turbo run build:android -yarn turbo run build:ios -``` - -## Architecture - -The SDK follows a standard React Native bridge pattern with four feature managers: - -**TypeScript layer** (`src/managers/`): `auth.ts`, `payment.ts`, `reader.ts`, `settings.ts` — each wraps `NativeModules.MobilePaymentsSdkReactNative` and provides typed APIs. Entry point is `src/index.tsx` which re-exports everything. - -**iOS native** (`ios/`): `MobilePaymentsSdkReactNative.swift` extends `RCTEventEmitter`. `Mappers.swift` converts native objects to JS-compatible dictionaries. `Extensions.swift` has helpers. Bridging header in `.mm` file. - -**Android native** (`android/src/main/java/com/mobilepaymentssdkreactnative/`): `MobilePaymentsSdkReactNativeModule.kt` uses `@ReactMethod` annotations. `Utils.kt` has mapping/conversion helpers. Uses Kotlin coroutines for async. - -**Events emitted to JS**: `AuthorizationStatusChange`, `ReaderChanged` — both platforms emit these through the RN event system. - -**Models** (`src/models/`): `enums.ts` (authorization states, card brands, reader states), `objects.ts` (Location, Money, Payment, Reader types), `errors.ts` (error definitions). - -## Platform Constraints - -- **iOS**: Min deployment target 16. Depends on `SquareMobilePaymentsSDK ~> 2.6.0` and `MockReaderUI ~> 2.6.0`. -- **Android**: Min SDK 28, Compile/Target SDK 36 (required by `androidx.core:core:1.18.0`, pulled in by native SDK 2.6.0). AGP 8.9.1+ / Gradle 8.13+. Kotlin 2.2.21. Proguard must be disabled (`minifyEnabled: false`). -- **Kotlin 2.2.x**: React Native 0.75.x Gradle plugin doesn't natively support Kotlin 2.2.x — requires `patch-package` workaround (see `docs/KOTLIN_COMPATIBILITY.md`). -- Not compatible with Expo Go (requires native code). -- Supports both old (bridge) and new (TurboModule) React Native architectures. - -## Example App Setup - -Square credentials must be configured in platform-specific files: -- **iOS**: `example/ios/MobilePaymentsSdkReactNativeExample/Config.m` -- **Android**: `example/android/app/src/main/java/mobilepaymentssdkreactnative/example/MainApplication.kt` -- **Access Token & Location ID**: `example/src/Screens/PermissionsScreen.tsx` - -## Code Style - -- ESLint with `@react-native` config + Prettier -- 2-space indentation, single quotes, trailing commas (es5) -- TypeScript strict mode enabled diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING_AGENTS.md b/CONTRIBUTING_AGENTS.md new file mode 100644 index 0000000..819715f --- /dev/null +++ b/CONTRIBUTING_AGENTS.md @@ -0,0 +1,68 @@ +# CONTRIBUTING_AGENTS.md + +Guidance for coding agents **working on this wrapper** — building it, testing it, and changing the +bridge between the TypeScript layer and the native SDKs. + +> **Integrating the SDK into your own app instead?** Read [AGENTS.md](AGENTS.md). It covers install +> coordinates, build constraints, credentials, permissions, and the authorization ordering rule. +> None of the commands below are needed to consume the package from npm. + +## Project Overview + +React Native plugin wrapping Square's Mobile Payments SDK for in-person payments on iOS and Android. Monorepo with the library at root and a "Donut Counter" example app in `example/`. + +## Common Commands + +```bash +yarn # Install all dependencies (must use yarn, not npm) +yarn prepare # Build library (bob build) → outputs to lib/ +yarn test # Run Jest tests +yarn test --testPathPattern= # Run a single test file +yarn typecheck # TypeScript type checking +yarn lint # ESLint + Prettier +yarn lint --fix # Auto-fix lint issues + +# Example app +yarn example start # Start Metro bundler +yarn example ios # Build & run on iOS +yarn example android # Build & run on Android + +# CI-style cached builds +yarn turbo run build:android +yarn turbo run build:ios +``` + +## Architecture + +The SDK follows a standard React Native bridge pattern with four feature managers: + +**TypeScript layer** (`src/managers/`): `auth.ts`, `payment.ts`, `reader.ts`, `settings.ts` — each wraps `NativeModules.MobilePaymentsSdkReactNative` and provides typed APIs. Entry point is `src/index.tsx` which re-exports everything. + +**iOS native** (`ios/`): `MobilePaymentsSdkReactNative.swift` extends `RCTEventEmitter`. `Mappers.swift` converts native objects to JS-compatible dictionaries. `Extensions.swift` has helpers. Bridging header in `.mm` file. + +**Android native** (`android/src/main/java/com/mobilepaymentssdkreactnative/`): `MobilePaymentsSdkReactNativeModule.kt` uses `@ReactMethod` annotations. `Utils.kt` has mapping/conversion helpers. Uses Kotlin coroutines for async. + +**Events emitted to JS**: `AuthorizationStatusChange`, `ReaderChanged` — both platforms emit these through the RN event system. + +**Models** (`src/models/`): `enums.ts` (authorization states, card brands, reader states), `objects.ts` (Location, Money, Payment, Reader types), `errors.ts` (error definitions). + +## Platform Constraints + +- **iOS**: Min deployment target 16. Depends on `SquareMobilePaymentsSDK ~> 2.6.0` and `MockReaderUI ~> 2.6.0`. +- **Android**: Min SDK 28, Compile/Target SDK 36 (required by `androidx.core:core:1.18.0`, pulled in by native SDK 2.6.0). AGP 8.9.1+ / Gradle 8.13+. Kotlin 2.2.21. Proguard must be disabled (`minifyEnabled: false`). +- **Kotlin 2.2.x**: React Native 0.75.x Gradle plugin doesn't natively support Kotlin 2.2.x — requires `patch-package` workaround (see `docs/KOTLIN_COMPATIBILITY.md`). +- Not compatible with Expo Go (requires native code). +- Supports both old (bridge) and new (TurboModule) React Native architectures. + +## Example App Setup + +Square credentials must be configured in platform-specific files: +- **iOS**: `example/ios/MobilePaymentsSdkReactNativeExample/Config.m` +- **Android**: `example/android/app/src/main/java/mobilepaymentssdkreactnative/example/MainApplication.kt` +- **Access Token & Location ID**: `example/src/Screens/PermissionsScreen.tsx` + +## Code Style + +- ESLint with `@react-native` config + Prettier +- 2-space indentation, single quotes, trailing commas (es5) +- TypeScript strict mode enabled