diff --git a/CHANGELOG.md b/CHANGELOG.md index f10b217..8033059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## Changelog +### v2026.1.1 Jan 31, 2026 + +* Upgrade Android SDK to 2.4.0 +* Upgrade iOS SDK to 2.4.0 + ### v2025.2.1 Feb 19, 2025 * Address NPM dependency issue diff --git a/android/build.gradle b/android/build.gradle index c1cf90f..49de68f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -89,6 +89,16 @@ repositories { def kotlin_version = getExtOrDefault("kotlinVersion") +// Align Kotlin stdlib with compiler to avoid "metadata version 2.2.0, expected 2.0.0" when +// other dependencies pull in a newer stdlib +configurations.all { + resolutionStrategy { + force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + } +} + dependencies { // For < 0.71, this will be from the local maven repo // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin diff --git a/android/gradle.properties b/android/gradle.properties index 60d8881..c030b30 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,4 +2,4 @@ MobilePaymentsSdkReactNative_kotlinVersion=2.0.21 MobilePaymentsSdkReactNative_minSdkVersion=28 MobilePaymentsSdkReactNative_targetSdkVersion=35 MobilePaymentsSdkReactNative_compileSdkVersion=35 -MobilePaymentsSdkReactNative_ndkversion=26.1.10909125 +MobilePaymentsSdkReactNative_ndkversion=28.0.12433566 diff --git a/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt b/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt index 227dbe8..950dee1 100644 --- a/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt +++ b/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt @@ -61,10 +61,10 @@ fun ReadableMap.readPaymentParameters(): PaymentParameters { val statementDescription = getString("statementDescription") val teamMemberId = getString("teamMemberId") val tipMoney = convertToMoney(getMap("tipMoney")) - val idempotencyKey = getString("idempotencyKey") val paymentAttemptId = getString("paymentAttemptId") + ?: java.util.UUID.randomUUID().toString() - val builder = PaymentParameters.Builder(amountMoney, processingMode, allowCardSurcharge) + val builder = PaymentParameters.Builder(amountMoney, processingMode, allowCardSurcharge, paymentAttemptId) acceptPartialAuthorization?.let { builder.acceptPartialAuthorization(it) } appFeeMoney?.let { builder.appFeeMoney(it) } autocomplete?.let { builder.autocomplete(it) } @@ -78,8 +78,6 @@ fun ReadableMap.readPaymentParameters(): PaymentParameters { statementDescription?.let { builder.statementDescription(it) } teamMemberId?.let { builder.teamMemberId(it) } tipMoney?.let { builder.tipMoney(it) } - idempotencyKey?.let { builder.idempotencyKey(it) } - paymentAttemptId?.let { builder.paymentAttemptId(it) } return builder.build() } @@ -330,7 +328,7 @@ fun ReaderInfo.toReaderInfoMap(): WritableMap { return WritableNativeMap().apply { putString("id", id) putString("model", model.toModelString()) - putString("state", state.toStateString()) + putString("state", status.toStatusString()) putString("status", status.toStatusString()) putString("serialNumber", serialNumber) putString("name", name) diff --git a/example/README.md b/example/README.md index bd25f4f..68e648e 100644 --- a/example/README.md +++ b/example/README.md @@ -35,6 +35,7 @@ The example app makes the following assumptions: * Clone this repo (if you have not already): `https://github.com/square/mobile-payments-sdk-react-native` * Make sure you've set up your environment for developing in React Native, by visiting the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) guide. +* **Android 16KB:** The example is configured for 16KB page size support (NDK r28, Build Tools 35). Install **NDK 28.0.12433566** (or newer r28) and **Build Tools 35.0.0** via Android Studio SDK Manager if needed. ## 2. Get application credentials In your [Developer Dashboard](https://developer.squareup.com/apps), create an application or open an existing one you would like to use. If this is your first time creating an application with Square, you can review this [Get Started](https://developer.squareup.com/docs/square-get-started) guide for more information. @@ -53,7 +54,7 @@ Click "Locations" in the left navigation and make note of the Default Test Accou ## 4. Run the app 1. Make sure you're in the root folder of the repository. 2. Run `yarn`, then `yarn example start`. This will start `Metro`, the JavaScript _bundler_ that ships _with_ React Native. -3. Once Metro has loaded, select the plaform of your choice: `a` for Android, `i` for iOS. +3. Once Metro has loaded, select the platform of your choice: `a` for Android, `i` for iOS. You can also run the app for each individual platform by opening `ios/MobilePaymentsSdkReactNativeExample.xcworkspace` in Xcode for iOS; or `android/build.gradle` in Android Studio, for Android. diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ad17ffe..c54b2c0 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -2,6 +2,17 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" +// Align Kotlin stdlib with compiler (2.0.21) to avoid "metadata 2.2.0, expected 2.0.0" when +// dependencies pull in a newer stdlib +def kotlinVersion = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : "2.0.21" +configurations.all { + resolutionStrategy { + force "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}" + } +} + /* Load custom properties for the app, if any */ Properties localProperties = new Properties() if (project.file("app.properties").exists()) { @@ -117,12 +128,19 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } } dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") + // Mobile Payments SDK 2.4.0 implementation("com.squareup.sdk:mobile-payments-sdk:$squareSdkVersion") + implementation("com.squareup.sdk:mockreader-ui:$squareSdkVersion") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { diff --git a/example/android/build.gradle b/example/android/build.gradle index 39e2407..0ba744d 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { ext { - buildToolsVersion = "34.0.0" + buildToolsVersion = "35.0.0" minSdkVersion = 28 compileSdkVersion = 35 targetSdkVersion = 35 - ndkVersion = "26.1.10909125" + ndkVersion = "28.0.12433566" kotlinVersion = "2.0.21" - squareSdkVersion = "2.3.4" + squareSdkVersion = "2.4.0" } repositories { google() diff --git a/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.h b/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.h index eaeeec3..bb30ad8 100644 --- a/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.h +++ b/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.h @@ -1,7 +1,7 @@ #import -#import #import #import +#import "SquareSDKInitializer.h" @interface AppDelegate : RCTAppDelegate diff --git a/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.mm b/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.mm index ed2ff62..601b7fc 100644 --- a/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.mm +++ b/example/ios/MobilePaymentsSdkReactNativeExample/AppDelegate.mm @@ -7,7 +7,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSString *APP_ID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"APP_ID"]; - [SQMPMobilePaymentsSDK initializeWithApplicationLaunchOptions:launchOptions squareApplicationID: APP_ID]; + MPRNInitializeSquareSDK(launchOptions, APP_ID); self.moduleName = @"MobilePaymentsSdkReactNativeExample"; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 1dacb30..4b3ea39 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -11,7 +11,7 @@ PODS: - DoubleConversion - glog - hermes-engine - - MockReaderUI (~> 2.3.1) + - MockReaderUI (~> 2.4.0) - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety @@ -28,9 +28,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SquareMobilePaymentsSDK (~> 2.3.1) + - SquareMobilePaymentsSDK (~> 2.4.0) - Yoga - - MockReaderUI (2.3.1) + - MockReaderUI (2.4.0) - Permission-BluetoothPeripheral (3.10.1): - RNPermissions - Permission-LocationAccuracy (3.10.1): @@ -1581,7 +1581,7 @@ PODS: - ReactCommon/turbomodule/core - Yoga - SocketRocket (0.7.0) - - SquareMobilePaymentsSDK (2.3.1) + - SquareMobilePaymentsSDK (2.4.0) - Yoga (0.0.0) DEPENDENCIES: @@ -1823,8 +1823,8 @@ SPEC CHECKSUMS: fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: 69ef571f3de08433d766d614c73a9838a06bf7eb hermes-engine: 8d2103d6c0176779aea4e25df6bb1410f9946680 - mobile-payments-sdk-react-native: 14d13486a0252d5a56cd6b1bfbd2e2f10c26b11a - MockReaderUI: 9307e00522d5ff4aa6fb5c012205af1bbab7dc70 + mobile-payments-sdk-react-native: 383bbd2a98346a3a66ed5b72581a97bba781ae75 + MockReaderUI: e527a5bc446b95e8cd5ddb4c565fc7d614d38a0f Permission-BluetoothPeripheral: 34ab829f159c6cf400c57bac05f5ba1b0af7a86e Permission-LocationAccuracy: 30c5421911024b28d8916db5cbd728097da54434 Permission-LocationAlways: af165dee8a5a5888df6764f9f6ba98b112893709 @@ -1891,7 +1891,7 @@ SPEC CHECKSUMS: RNScreens: 35bb8e81aeccf111baa0ea01a54231390dbbcfd9 RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - SquareMobilePaymentsSDK: f53082b4972428724c629f41882e11e6152c7ec3 + SquareMobilePaymentsSDK: 53cd315a537037377cd364edc874bf75782239eb Yoga: 4ef80d96a5534f0e01b3055f17d1e19a9fc61b63 PODFILE CHECKSUM: 81d2f9e3393262e2bc7a99478a8e3a6741f0482e diff --git a/ios/Extensions.swift b/ios/Extensions.swift index f1ebe3a..45b2154 100644 --- a/ios/Extensions.swift +++ b/ios/Extensions.swift @@ -66,27 +66,6 @@ extension CardInputMethods { } } -extension ReaderState { - func toName() -> String { - return switch self { - case .connecting: - "CONNECTING" - case .disabled: - "DISABLED" - case .disconnected: - "DISCONNECTED" - case .failedToConnect: - "FAILED_TO_CONNECT" - case .ready: - "READY" - case .updatingFirmware: - "UPDATING_FIRMWARE" - default: - "UNKNOWN" - } - } -} - extension ReaderModel { func toName() -> String { return switch self { @@ -116,96 +95,6 @@ extension ReaderFirmwareInfo { } } -extension ReaderConnectionFailureReason { - func toName() -> String { - return switch self { - case .deniedByServer: - "DENIED_BY_SERVER" - case .genericError: - "GENERIC_ERROR" - case .maxReadersConnected: - "MAX_READERS_CONNECTED" - case .networkTimeout: - "NETWORK_TIMEOUT" - case .networkTransportError: - "NETWORK_TRANSPORT_ERROR" - case .notConnectedToInternet: - "NOT_CONNECTED_TO_INTERNET" - case .readerTimeout: - "READER_TIMEOUT" - case .revokedByDevice: - "REVOKED_BY_DEVICE" - case .serverError: - "SERVER_ERROR" - case .tapToPayError: - "TAP_TO_PAY_ERROR" - case .unknown: - "UNKNOWN" - default : "UNKNOWN" - } - } -} - - -extension ReaderConnectionFailureRecoverySuggestion { - func toName() -> String { - return switch self { - case .activateAccount: - "ACTIVATE_ACCOUNT" - case .contactSupport: - "CONTACT_SUPPORT" - case .enablePasscodeToUseTapToPay: - "ENABLE_PASSCODE_TO_USE_TAP_TO_PAY" - case .noSuggestion: - "NO_SUGGESTION" - case .retry: - "RETRY" - case .reviewTapToPayGuidelines: - "REVIEW_TAP_TO_PAY_GUIDELINES" - default: - "UNKNOWN" - } - } -} - - -extension ReaderConnectionFailureInfo { - func toMap() -> NSDictionary { - return [ - "failureReason" : failureReason.toName(), - "localizedDescription" : localizedDescription, - "localizedTitle" : localizedTitle, - "recoverySuggestion": recoverySuggestion.toName() - ] - } -} - -extension ReaderConnectionState { - func toName() -> String { - return switch self { - case .connected: - "CONNECTED" - case .connecting: - "CONNECTING" - case .failedToConnect: - "FAILED_TO_CONNECT" - case .notConnected: - "NOT_CONNECTED" - default: - "UNKNOWN" - } - } -} - -extension ReaderConnectionInfo { - func toMap() -> NSDictionary { - return [ - "failureInfo" : failureInfo?.toMap() ?? NSNull(), - "state" : state.toName() - ] - } -} - extension CardInsertionStatus { func toName() -> String { @@ -245,6 +134,18 @@ extension ReaderBatteryStatus { } } +extension ReaderStatusInfo { + func toMap() -> NSDictionary { + return [ + "status" : status.rawValue, + "statusDescription" : status.description, + "unavailableReasonInfo" : unavailableReasonInfo?.reason.rawValue ?? NSNull(), + "unavailableReasonInfoTitle" : unavailableReasonInfo?.title ?? NSNull(), + "unavailableReasonInfoDescription" : unavailableReasonInfo?.description ?? NSNull(), + ] + } +} + extension ReaderChange { func toName() -> String { switch self { @@ -258,16 +159,10 @@ extension ReaderChange { return "CARD_INSERTED" case .cardRemoved: return "CARD_REMOVED" - case .connectionDidFail: - return "CONNECTION_DID_FAILED" - case .connectionStateDidChange: - return "CONNECTION_STATE_DID_CHANGED" case .firmwareUpdateDidFail: return "FIRMWARE_UPDATE_DID_FAILED" case .firmwareUpdatePercentDidChange: return "FIRMWARE_UPDATE_PERCENT_DID_CHANGED" - case .stateDidChange: - return "STATE_DID_CHANGED" default: return "UNKNOWN" } } diff --git a/ios/Mappers.swift b/ios/Mappers.swift index d8ff1f8..0daa147 100644 --- a/ios/Mappers.swift +++ b/ios/Mappers.swift @@ -45,14 +45,6 @@ class Mappers { processingMode: processingMode ) } - else if let idempotencyKey = paymentParameters["idempotencyKey"] as? String { - paymentParams = PaymentParameters( - idempotencyKey: idempotencyKey, - amountMoney: amountMoney, - processingMode: processingMode - ) - } - guard let paymentParams else { return .failure(.missingPaymentAttemptIdOrIdempotencyKey) @@ -143,7 +135,6 @@ class Mappers { return [ "batteryStatus" : reader.batteryStatus?.toMap() ?? NSNull(), "cardInsertionStatus": reader.cardInsertionStatus.toName(), - "connectionInfo": reader.connectionInfo.toMap(), "firmwareInfo" : reader.firmwareInfo?.toMap() ?? NSNull(), "id": String(reader.id), "isBlinkable" : reader.isBlinkable, @@ -152,7 +143,7 @@ class Mappers { "model": reader.model.toName(), "name" : reader.name, "serialNumber" : reader.serialNumber ?? NSNull(), - "state" : reader.state.toName(), + "statusInfo" : reader.statusInfo.toMap(), "supportedCardEntryMethods" : reader.supportedInputMethods.toList() ] } diff --git a/ios/MobilePaymentsSdkReactNative.swift b/ios/MobilePaymentsSdkReactNative.swift index f3d0e39..440d16e 100644 --- a/ios/MobilePaymentsSdkReactNative.swift +++ b/ios/MobilePaymentsSdkReactNative.swift @@ -579,8 +579,6 @@ extension MobilePaymentsSdkReactNative: PaymentManagerDelegate { switch paymentError { case .deviceTimeDoesNotMatchServerTime: errorMessage = "The local device time is out of sync with the server time, which could lead to inaccurate payment reporting. Check your device's time and attempt your action again." - case .idempotencyKeyReused: - errorMessage = "The idempotency key used for this payment has already been used. Review previous payments to ensure you are not processing a duplicate payment, and then try again with a new idempotency key." case .invalidPaymentParameters: errorMessage = "The PaymentParameters provided were invalid. Check the request details and try the payment again." case .invalidPaymentSource: diff --git a/ios/Podfile b/ios/Podfile index 577e102..de7ee10 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -7,7 +7,7 @@ target 'RNMobilePaymentSDK' do use_frameworks! # Pods for RNMobilePaymentSDK - pod "SquareMobilePaymentsSDK", "~> 2.3.1" + pod "SquareMobilePaymentsSDK", "~> 2.4.0" target 'RNMobilePaymentSDKTests' do inherit! :search_paths # Pods for testing diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 512a7dd..4da1b9c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - SquareMobilePaymentsSDK (2.3.1) + - SquareMobilePaymentsSDK (2.4.0) DEPENDENCIES: - - SquareMobilePaymentsSDK (~> 2.3.1) + - SquareMobilePaymentsSDK (~> 2.4.0) SPEC REPOS: trunk: - SquareMobilePaymentsSDK SPEC CHECKSUMS: - SquareMobilePaymentsSDK: f53082b4972428724c629f41882e11e6152c7ec3 + SquareMobilePaymentsSDK: 53cd315a537037377cd364edc874bf75782239eb -PODFILE CHECKSUM: 234882d7834790af146a9dbf17b3c978fb017aae +PODFILE CHECKSUM: 90edd7b3e424a600173ffd8b1fcb677878cc582e COCOAPODS: 1.16.2 diff --git a/ios/SquareSDKInitializer.h b/ios/SquareSDKInitializer.h new file mode 100644 index 0000000..e06657c --- /dev/null +++ b/ios/SquareSDKInitializer.h @@ -0,0 +1,18 @@ +// +// SquareSDKInitializer.h +// mobile-payments-sdk-react-native +// +// Call this from your AppDelegate before starting React Native. +// Do NOT import SquareMobilePaymentsSDK in your app—this module provides +// the only SDK dependency to avoid duplicate type definitions. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Initializes the Square Mobile Payments SDK. Call from AppDelegate +/// - (BOOL)application:didFinishLaunchingWithOptions: before [super application:...]. +void MPRNInitializeSquareSDK(NSDictionary * _Nullable launchOptions, NSString *applicationId); + +NS_ASSUME_NONNULL_END diff --git a/ios/SquareSDKInitializer.mm b/ios/SquareSDKInitializer.mm new file mode 100644 index 0000000..2be9ad5 --- /dev/null +++ b/ios/SquareSDKInitializer.mm @@ -0,0 +1,12 @@ +// +// SquareSDKInitializer.mm +// mobile-payments-sdk-react-native +// + +#import "SquareSDKInitializer.h" +#import + +void MPRNInitializeSquareSDK(NSDictionary * _Nullable launchOptions, NSString *applicationId) { + [SQMPMobilePaymentsSDK initializeWithApplicationLaunchOptions:launchOptions + squareApplicationID:applicationId]; +} diff --git a/mobile-payments-sdk-react-native.podspec b/mobile-payments-sdk-react-native.podspec index 49df639..d2a1077 100644 --- a/mobile-payments-sdk-react-native.podspec +++ b/mobile-payments-sdk-react-native.podspec @@ -14,8 +14,8 @@ Pod::Spec.new do |s| s.platforms = { :ios => "16" } s.source = { :git => "https://github.com/square//mobile-payments-sdk-react-native.git", :tag => "#{s.version}" } - s.dependency "MockReaderUI", "~> 2.3.1" - s.dependency "SquareMobilePaymentsSDK", "~> 2.3.1" + s.dependency "MockReaderUI", "~> 2.4.0" + s.dependency "SquareMobilePaymentsSDK", "~> 2.4.0" s.source_files = "ios/**/*.{h,m,mm,swift}" diff --git a/package.json b/package.json index 4f592ef..4f9cbc6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mobile-payments-sdk-react-native", - "version": "2025.9.1", + "version": "2026.1.1", "description": "Mobile Payments SDK plug-in for React Native. Enables developers to build secure in-person payment solutions.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js",