diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d8b4f..e73860d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ ## Changelog +### Unreleased + +This release only adds APIs and fields. No existing signature, type or value changes. + +* Add `SettingsManager.getSdkSettings()`, which returns a new `SdkSettings` object (`sdkVersion`, `sdkEnvironment`, `securityComplianceVersion`) +* Add `SettingsManager.getTrackingConsentState()` and `SettingsManager.updateTrackingConsent(granted)`, with a new `TrackingConsentState` enum +* Add `PaymentManager.getIdempotencyKey(paymentAttemptId)`, which returns the idempotency key the SDK generated for a payment attempt +* Add `PaymentManager.isPaymentInProgress()` +* Add `PaymentManager.getAvailableCardEntryMethods()` and `PaymentManager.setAvailableCardEntryMethodsCallback(callback)`, to know which card entry methods the connected hardware accepts and be notified when they change +* **Android:** Add `PaymentManager.completePayment(paymentId)` to capture a payment taken with `autocomplete: false`. It takes the server-assigned `Payment.id`, not the `paymentAttemptId` +* **Android:** Add `PaymentManager.getAllIdempotencyKeys()`, which returns a list of the new `IdempotencyKeyData` object +* Add `ReaderManager.retryConnection(id)`, which returns a new `RetryConnectionResult` enum, and `ReaderManager.rebootReader(id)` (iOS only) +* Add `ReaderManager.setPreferredFirmwareUpdateTime(time)` and `ReaderManager.setReducedChargingModeEnabled(enabled)`, so `ReaderSettings` can be changed and not only read +* Payments now include `status`, `cardDetails`, `customerId`, `note`, `teamMemberId`, `cashDetails` and a `paymentType` of `ONLINE` or `OFFLINE`. Both platforms previously returned only the base fields. New `CashPaymentDetails`, `CardSurchargeDetails`, `Capabilities` and `PaymentProcessingFee` objects, and `PaymentType`, `PaymentCapability`, `PaymentProcessingFeeType`, `VerificationMethod` and `VerificationResult` enums +* **Android:** Online payments also include `receiptNumber`, `receiptUrl`, `statementDescription`, `capabilities` and `processingFee`, and card details include `verificationMethod`, `verificationResults` and `Card.bin` +* **iOS:** `OfflinePaymentQueue.getPayments()` now includes `localId`, `status`, `uploadedAt` and `cardDetails` +* `ReaderInfo` now includes `connectionType` and, on iOS, `isRebootable`. `ReaderFirmwareInfo` includes `updateStatus` and `updateTime`, and the reader status includes `title` and `detail` on iOS. New `ReaderConnectionType` and `FirmwareUpdateStatus` enums +* **Android:** `Location` now includes `merchantId`, `businessName` and `cardProcessingActivated` + ### v2026.8.1 Aug 24, 2026 * Upgrade Android native SDK to `2.6.1`, which fixes a bug where Tap to Pay on Android became unavailable for up to 2 hours before the daily payment-key rotation diff --git a/android/src/main/java/com/mobilepaymentssdkreactnative/MobilePaymentsSdkReactNativeModule.kt b/android/src/main/java/com/mobilepaymentssdkreactnative/MobilePaymentsSdkReactNativeModule.kt index cb540b8..a4804b8 100644 --- a/android/src/main/java/com/mobilepaymentssdkreactnative/MobilePaymentsSdkReactNativeModule.kt +++ b/android/src/main/java/com/mobilepaymentssdkreactnative/MobilePaymentsSdkReactNativeModule.kt @@ -28,6 +28,7 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat private var paymentHandle: PaymentHandle? = null private var authStateCallback: CallbackReference? = null private var readerChangedCallbacks = mutableMapOf(); + private var cardEntryMethodsCallbacks = mutableMapOf(); private var pairingHandler: PairingHandle? = null override fun getName(): String { @@ -143,6 +144,25 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat } } + @ReactMethod + fun getSdkSettings(promise: Promise) { + val settingsManager = MobilePaymentsSdk.settingsManager() + promise.resolve(settingsManager.getSdkSettings().toSdkSettingsMap()) + } + + @ReactMethod + fun getTrackingConsentState(promise: Promise) { + val settingsManager = MobilePaymentsSdk.settingsManager() + promise.resolve(settingsManager.trackingConsentState.toTrackingConsentStateString()) + } + + @ReactMethod + fun updateTrackingConsent(granted: Boolean, promise: Promise) { + val settingsManager = MobilePaymentsSdk.settingsManager() + settingsManager.updateTrackingConsent(granted) + promise.resolve(null) + } + @ReactMethod fun showMockReaderUI(promise: Promise) { if (!MobilePaymentsSdk.isSandboxEnvironment()) { @@ -278,6 +298,88 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat } } + @ReactMethod + fun getIdempotencyKey(paymentAttemptId: String, promise: Promise) { + val paymentManager = MobilePaymentsSdk.paymentManager() + when (val result = paymentManager.getIdempotencyKey(paymentAttemptId)) { + is Success -> promise.resolve(result.value) + is Failure -> + promise.reject("GET_IDEMPOTENCY_KEY_FAILED", result.errorMessage, result.toErrorMap()) + } + } + + @ReactMethod + fun getAllIdempotencyKeys(promise: Promise) { + reactContext.runOnUiQueueThread { + val paymentManager = MobilePaymentsSdk.paymentManager() + paymentManager.getAllIdempotencyKeys { result -> + when (result) { + is Success -> { + val keys = Arguments.createArray() + result.value.forEach { keys.pushMap(it.toIdempotencyKeyDataMap()) } + promise.resolve(keys) + } + is Failure -> + promise.reject("GET_IDEMPOTENCY_KEYS_FAILED", result.errorMessage, result.toErrorMap()) + } + } + } + } + + @ReactMethod + fun completePayment(paymentId: String, promise: Promise) { + reactContext.runOnUiQueueThread { + val paymentManager = MobilePaymentsSdk.paymentManager() + paymentManager.completePayment(paymentId) { result -> + when (result) { + is Success -> promise.resolve(result.value.toPaymentMap()) + is Failure -> + promise.reject("COMPLETE_PAYMENT_FAILED", result.errorMessage, result.toErrorMap()) + } + } + } + } + + @ReactMethod + fun isPaymentInProgress(promise: Promise) { + reactContext.runOnUiQueueThread { + val paymentManager = MobilePaymentsSdk.paymentManager() + promise.resolve(paymentManager.currentPaymentHandle != null) + } + } + + @ReactMethod + fun getAvailableCardEntryMethods(promise: Promise) { + val paymentManager = MobilePaymentsSdk.paymentManager() + promise.resolve(paymentManager.getAvailableCardEntryMethods().toCardEntryMethodsArray()) + } + + @ReactMethod + fun addAvailableCardEntryMethodsCallback(refId: String, promise: Promise) { + val paymentManager = MobilePaymentsSdk.paymentManager() + val ref = paymentManager.setAvailableCardEntryMethodChangedCallback { methods -> + emitEvent( + reactContext, + "AvailableCardEntryMethodsChanged-${refId}", + WritableNativeMap().apply { + putArray("methods", methods.toCardEntryMethodsArray()) + } + ) + } + cardEntryMethodsCallbacks.put(refId, ref) + promise.resolve(refId) + } + + @ReactMethod + fun removeAvailableCardEntryMethodsCallback(refId: String, promise: Promise) { + val ref = cardEntryMethodsCallbacks.get(refId) + if (ref != null) { + ref.clear() + cardEntryMethodsCallbacks.remove(refId) + } + promise.resolve(null) + } + @ReactMethod fun getReaders(promise: Promise) { val readerManager = MobilePaymentsSdk.readerManager() @@ -344,6 +446,26 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat } // --- + @ReactMethod + fun setPreferredFirmwareUpdateTime(time: ReadableMap?, promise: Promise) { + val timeOfDay = try { + time?.toTimeOfDay() + } catch (e: IllegalArgumentException) { + promise.reject("INVALID_TIME_OF_DAY", "hour and minute are required and must form a valid time of day", e) + return + } + val readerManager = MobilePaymentsSdk.readerManager() + readerManager.readerSettings.preferredFirmwareUpdateTime = timeOfDay + promise.resolve(null) + } + + @ReactMethod + fun setReducedChargingModeEnabled(enabled: Boolean, promise: Promise) { + val readerManager = MobilePaymentsSdk.readerManager() + readerManager.readerSettings.isReducedChargingModeEnabled = enabled + promise.resolve(null) + } + @ReactMethod fun retryConnection(id: String, promise: Promise) { val readerManager = MobilePaymentsSdk.readerManager() diff --git a/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt b/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt index eada4d1..a2e6787 100644 --- a/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt +++ b/android/src/main/java/com/mobilepaymentssdkreactnative/Utils.kt @@ -17,11 +17,16 @@ import com.squareup.sdk.mobilepayments.core.Result.Failure import com.squareup.sdk.mobilepayments.payment.AdditionalPaymentMethod import com.squareup.sdk.mobilepayments.payment.AdditionalPaymentMethod.Type import com.squareup.sdk.mobilepayments.payment.CurrencyCode +import com.squareup.sdk.mobilepayments.settings.SdkSettings +import com.squareup.sdk.mobilepayments.settings.TrackingConsentState import com.squareup.sdk.mobilepayments.payment.DelayAction import com.squareup.sdk.mobilepayments.payment.Money import com.squareup.sdk.mobilepayments.payment.CardPaymentDetails +import com.squareup.sdk.mobilepayments.payment.CashPaymentDetails +import com.squareup.sdk.mobilepayments.payment.PaymentProcessingFee import com.squareup.sdk.mobilepayments.payment.Card import com.squareup.sdk.mobilepayments.payment.Payment +import com.squareup.sdk.mobilepayments.payment.PaymentManager import com.squareup.sdk.mobilepayments.payment.Payment.OfflineStatus import com.squareup.sdk.mobilepayments.payment.Payment.OfflinePayment import com.squareup.sdk.mobilepayments.payment.Payment.OnlinePayment @@ -205,9 +210,10 @@ fun Payment.toPaymentMap(): ReadableMap { putMap("tipMoney", tipMoney.toMoneyMap()) putMap("totalMoney", totalMoney.toMoneyMap()) putString("updatedAt", updatedAt.toIsoInstantString()) - //cashDetails + putMap("cashDetails", cashDetails?.toCashDetailsMap()) when (this@toPaymentMap) { is OfflinePayment -> { + putString("paymentType", "OFFLINE") putString("uploadedAt", uploadedAt?.toIsoInstantString()) putString("localId", localId) putString("id", id) @@ -215,19 +221,18 @@ fun Payment.toPaymentMap(): ReadableMap { putMap("cardDetails", cardDetails?.toCardDetailsMap()) } is OnlinePayment -> { + putString("paymentType", "ONLINE") putString("id", id) - /*processingFee - status - cardDetails - customerId - note - statementDescription - teamMemberId - capabilities - receiptNumber - remainingBalance - squareAccountDetails - digitalWalletDetails*/ + putString("status", status.toPaymentStatusString()) + putMap("cardDetails", cardDetails?.toCardDetailsMap()) + putString("customerId", customerId) + putString("note", note) + putString("teamMemberId", teamMemberId) + putString("statementDescription", statementDescription) + putString("receiptNumber", receiptNumber) + putString("receiptUrl", receiptUrl) + putMap("capabilities", capabilities.toCapabilitiesMap()) + putArray("processingFee", processingFee.toProcessingFeeArray()) } } } @@ -254,8 +259,11 @@ private fun CardPaymentDetails.toCardDetailsMap(): ReadableMap { is CardPaymentDetails.OnlineCardPaymentDetails -> { putString("applicationIdentifier", applicationId) putString("applicationName", applicationName) - //authorizationCode - //Status + putString("authorizationCode", authorizationCode) + putString("status", status.toCardPaymentStatusString()) + putMap("appliedCardSurchargeDetails", appliedCardSurchargeDetails?.toSurchargeDetailsMap()) + putString("verificationMethod", verificationMethod?.toVerificationMethodString()) + putString("verificationResults", verificationResults?.toVerificationResultString()) } } } @@ -270,6 +278,7 @@ private fun Card.toCardMap(): ReadableMap { putInt("expirationYear", expirationYear) putString("cardholderName", cardholderName) putString("id", id) + putString("bin", bin) } } @@ -319,6 +328,9 @@ fun AuthorizedLocation.toLocationMap(): WritableMap { putString("currencyCode", currencyCode.name) putString("name", name) putString("mcc", merchantId) + putString("merchantId", merchantId) + putString("businessName", businessName) + putBoolean("cardProcessingActivated", cardProcessingActivated) } } @@ -352,6 +364,7 @@ fun ReaderInfo.toReaderInfoMap(): WritableMap { putMap("status", status.toStatusMap()) putString("serialNumber", serialNumber) putString("name", name) + putString("connectionType", connectionType.toConnectionTypeString()) putMap("batteryStatus", batteryStatus?.toBatteryStatusMap()) putMap( "firmwareInfo", @@ -371,6 +384,8 @@ fun ReaderInfo.ReaderFirmwareInfo.toFirmwareInfoMap(): WritableMap { return WritableNativeMap().apply { putString("version", version) putInt("updatePercentage", updateStatus.toPercent() ?: 0) + putString("updateStatus", updateStatus.toFirmwareUpdateStatusString()) + putString("updateTime", updateStatus.toUpdateTime()?.toIsoInstantString()) putString("failureReason", null) } } @@ -492,3 +507,138 @@ fun RetryConnectionResult.toRetryConnectionResultString(): String = when (this) RetryConnectionResult.UNABLE_TO_RETRY -> "UNABLE_TO_RETRY" RetryConnectionResult.READER_NOT_FOUND -> "READER_NOT_FOUND" } + +fun SdkSettings.toSdkSettingsMap(): WritableMap { + return WritableNativeMap().apply { + putString("sdkVersion", sdkVersion) + putString("sdkEnvironment", sdkEnvironment.name) + putString("securityComplianceVersion", securityComplianceVersion) + } +} + +fun TrackingConsentState.toTrackingConsentStateString(): String { + return when (this) { + TrackingConsentState.PENDING -> "PENDING" + TrackingConsentState.GRANTED -> "GRANTED" + TrackingConsentState.DENIED -> "DENIED" + TrackingConsentState.NOT_REQUIRED -> "NOT_REQUIRED" + } +} + +fun PaymentManager.IdempotencyKeyData.toIdempotencyKeyDataMap(): WritableMap { + return WritableNativeMap().apply { + putString("idempotencyKey", idempotencyKey) + putString("paymentAttemptId", paymentAttemptId) + putString("updatedAt", updatedAt.toIsoInstantString()) + } +} + +fun Set.toCardEntryMethodsArray(): WritableNativeArray { + return WritableNativeArray().apply { + this@toCardEntryMethodsArray.forEach { pushString(it.toEntryMethodString()) } + } +} + +fun ReadableMap.toTimeOfDay(): TimeOfDay { + val hour = requireNotNull(getIntOrNull("hour")) { "hour is required" } + val minute = requireNotNull(getIntOrNull("minute")) { "minute is required" } + return TimeOfDay(hour, minute) +} + +fun ReaderInfo.FirmwareUpdateStatus.toFirmwareUpdateStatusString(): String { + return when(this) { + is ReaderInfo.FirmwareUpdateStatus.None -> "NONE" + is ReaderInfo.FirmwareUpdateStatus.Pending -> "PENDING" + is ReaderInfo.FirmwareUpdateStatus.InProgress -> "IN_PROGRESS" + } +} + +fun ReaderInfo.FirmwareUpdateStatus.toUpdateTime(): Date? { + return when(this) { + is ReaderInfo.FirmwareUpdateStatus.Pending -> this.updateDate + else -> null + } +} + +fun ReaderInfo.ConnectionType.toConnectionTypeString(): String { + return when(this) { + ReaderInfo.ConnectionType.USB -> "USB" + ReaderInfo.ConnectionType.BLUETOOTH -> "BLUETOOTH" + ReaderInfo.ConnectionType.AUDIO -> "AUDIO" + ReaderInfo.ConnectionType.EMBEDDED -> "EMBEDDED" + } +} + +private fun CashPaymentDetails.toCashDetailsMap(): ReadableMap { + return WritableNativeMap().apply { + putMap("buyerSuppliedMoney", buyerSuppliedMoney.toMoneyMap()) + putMap("changeBackMoney", changeBackMoney.toMoneyMap()) + } +} + +private fun Payment.Capabilities.toCapabilitiesMap(): ReadableMap { + return WritableNativeMap().apply { + putArray("allCapabilities", WritableNativeArray().apply { + allCapabilities.forEach { pushString(it) } + }) + } +} + +private fun Payment.Status.toPaymentStatusString(): String = + when (this) { + Payment.Status.APPROVED -> "APPROVED" + Payment.Status.COMPLETED -> "COMPLETED" + Payment.Status.CANCELED -> "CANCELED" + Payment.Status.FAILED -> "FAILED" + Payment.Status.UNKNOWN -> "UNKNOWN" + } + +private fun CardPaymentDetails.CardSurchargeDetails.toSurchargeDetailsMap(): ReadableMap { + return WritableNativeMap().apply { + putMap("cardSurchargeMoney", cardSurchargeMoney.toMoneyMap()) + putMap("taxOnSurchargeMoney", taxOnSurchargeMoney?.toMoneyMap()) + putMap("totalSurchargeMoney", totalSurchargeMoney.toMoneyMap()) + } +} + +private fun CardPaymentDetails.Status.toCardPaymentStatusString(): String = + when (this) { + CardPaymentDetails.Status.AUTHORIZED -> "AUTHORIZED" + CardPaymentDetails.Status.CAPTURED -> "CAPTURED" + CardPaymentDetails.Status.VOIDED -> "VOIDED" + CardPaymentDetails.Status.FAILED -> "FAILED" + } + +private fun CardPaymentDetails.VerificationMethod.toVerificationMethodString(): String = + when (this) { + CardPaymentDetails.VerificationMethod.PIN -> "PIN" + CardPaymentDetails.VerificationMethod.SIGNATURE -> "SIGNATURE" + CardPaymentDetails.VerificationMethod.PIN_AND_SIGNATURE -> "PIN_AND_SIGNATURE" + CardPaymentDetails.VerificationMethod.ON_DEVICE -> "ON_DEVICE" + CardPaymentDetails.VerificationMethod.NONE -> "NONE" + } + +private fun CardPaymentDetails.VerificationResult.toVerificationResultString(): String = + when (this) { + CardPaymentDetails.VerificationResult.SUCCESS -> "SUCCESS" + CardPaymentDetails.VerificationResult.FAILURE -> "FAILURE" + CardPaymentDetails.VerificationResult.UNKNOWN -> "UNKNOWN" + } + +private fun List.toProcessingFeeArray(): WritableNativeArray { + return WritableNativeArray().apply { + this@toProcessingFeeArray.forEach { fee -> + pushMap(WritableNativeMap().apply { + putString("effectiveAt", fee.effectiveAt.toIsoInstantString()) + putString("type", fee.type.toProcessingFeeTypeString()) + putMap("amountMoney", fee.amountMoney.toMoneyMap()) + }) + } + } +} + +private fun PaymentProcessingFee.Type.toProcessingFeeTypeString(): String = + when (this) { + PaymentProcessingFee.Type.INITIAL -> "INITIAL" + PaymentProcessingFee.Type.ADJUSTMENT -> "ADJUSTMENT" + } diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index fe8da8b..fb79e33 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -96,6 +96,12 @@ Method | Returns --------------------------------------------------------- | --------------------------------- | --- [startPayment](#startPayment) | Payment | Starts a payment taking payment and prompt parameters, and returns a Payment object, or an error including a PaymentError. [cancelPayment](#cancelPayment) | void | Cancels an ongoing payment, if possible. +[getIdempotencyKey](#getIdempotencyKey) | String \| null | Returns the idempotency key the SDK generated for a payment attempt. +[isPaymentInProgress](#isPaymentInProgress) | Boolean | Checks whether a payment is currently running. +[getAvailableCardEntryMethods](#getAvailableCardEntryMethods) | CardEntryMethod[] | Returns the card entry methods the connected hardware accepts right now. +[setAvailableCardEntryMethodsCallback](#setAvailableCardEntryMethodsCallback) | () => void | Notifies when the available card entry methods change. +[completePayment](#completePayment) | Payment | Captures a payment taken with `autocomplete: false` (Android only). +[getAllIdempotencyKeys](#getAllIdempotencyKeys) | IdempotencyKeyData[] | Lists every stored idempotency key (Android only). ## Method details @@ -114,6 +120,66 @@ promptParameters | PromptParameters | Parameters to configure the prompt. * **On success**: returns a Payment object, which will include all the information Square captured about the payment. * **On failure**: throws an error which includes the resulting PaymentError. +--- +### getIdempotencyKey + +Returns the idempotency key the SDK generated for the `CreatePayment` request of a payment attempt. The key is generated by the SDK and looked up by the `paymentAttemptId` passed in `PaymentParameters`. + +Parameter | Type | Description +--------- | ------ | ----------- +paymentAttemptId | String | The `paymentAttemptId` used to start the payment. + +* **On success**: returns the idempotency key, or `null` if none is stored for that attempt. +* **On failure**: throws an error which includes the resulting PaymentError. + +--- +### isPaymentInProgress + +Checks whether a payment is currently running. + +* **On success**: returns `true` if a payment is in progress. +* **On failure**: Not applicable. + +--- +### getAvailableCardEntryMethods + +Returns the card entry methods the connected hardware can accept right now, for instance to enable or disable your charge button. + +* **On success**: returns a list of `CardEntryMethod` (`EMV`, `CONTACTLESS`, `SWIPED`). +* **On failure**: Not applicable. + +--- +### setAvailableCardEntryMethodsCallback + +Registers a callback that receives the new list of card entry methods whenever it changes, for instance when a reader connects or disconnects. + +Parameter | Type | Description +--------- | ------ | ----------- +callback | (methods: CardEntryMethod[]) => void | Called with the available card entry methods. + +* **On success**: returns a function that removes the callback. +* **On failure**: Not applicable. + +--- +### completePayment + +Captures a payment that was taken with `autocomplete: false`. **Android only**; rejects on iOS, where the native SDK does not expose it. + +Parameter | Type | Description +--------- | ------ | ----------- +paymentId | String | The server-assigned `Payment.id` of the payment to complete, not the `paymentAttemptId`. + +* **On success**: returns the completed Payment. +* **On failure**: throws an error which includes the resulting PaymentError. + +--- +### getAllIdempotencyKeys + +Lists every idempotency key the SDK has stored, for reconciliation against the Payments API. **Android only**; rejects on iOS. + +* **On success**: returns a list of `IdempotencyKeyData`. +* **On failure**: throws an error which includes the resulting PaymentError. + --- ### Reader (reader.ts) @@ -123,6 +189,10 @@ Method | Returns --------------------------------------------------------- | --------------------------------- | --- [showMockReaderUI](#showMockReaderUI) | void | Shows the mock reader UI, which allows to connect mock readers and simulate card interactions. [hideMockReaderUI](#hideMockReaderUI) | void | Hides the mock reader UI. +[retryConnection](#retryConnection) | RetryConnectionResult | Retries the connection to a reader. +[rebootReader](#rebootReader) | void | Reboots a reader (iOS only). +[setPreferredFirmwareUpdateTime](#setPreferredFirmwareUpdateTime) | void | Sets the preferred time of day for reader firmware updates. +[setReducedChargingModeEnabled](#setReducedChargingModeEnabled) | void | Enables or disables reduced charging mode. ## Method details @@ -141,6 +211,54 @@ Dismisses the mock reader. * **On success**: dismisses the mock reader UI. * **On failure**: throws an error if the reader can't be dismissed, for instance, if it's not presented. +--- +### retryConnection + +Retries the connection to a reader whose `isConnectionRetryable` is `true`. + +Parameter | Type | Description +--------- | ------ | ----------- +id | String | The reader ID. + +* **On success**: returns a `RetryConnectionResult` (`STARTING_RECONNECTION`, `READER_ALREADY_CONNECTING_TO_SQUARE`, `UNABLE_TO_RETRY`, `READER_NOT_FOUND`). +* **On failure**: throws `READER_NOT_FOUND` if no reader has that ID. + +--- +### rebootReader + +Reboots a reader. **iOS only**; rejects on Android, where the native SDK has no equivalent. + +Parameter | Type | Description +--------- | ------ | ----------- +id | String | The reader ID. + +* **On success**: the reader starts rebooting. +* **On failure**: throws `READER_NOT_FOUND` if no reader has that ID, or `READER_NOT_REBOOTABLE` if `isRebootable` is `false`. + +--- +### setPreferredFirmwareUpdateTime + +Sets the preferred time of day at which readers install firmware updates. Pass `null` to clear it. + +Parameter | Type | Description +--------- | ------ | ----------- +time | TimeOfDay \| null | Hour (0-23) and minute (0-59). + +* **On success**: the setting is updated. +* **On failure**: throws `INVALID_TIME_OF_DAY` if the hour or minute is missing or out of range. + +--- +### setReducedChargingModeEnabled + +Enables or disables reduced charging mode on connected readers. + +Parameter | Type | Description +--------- | ------ | ----------- +enabled | Boolean | Whether reduced charging mode is enabled. + +* **On success**: the setting is updated. +* **On failure**: Not applicable. + --- ### Settings (reader.ts) @@ -151,6 +269,9 @@ Method | Returns [showSettings](#showSettings) | void | Shows the reader settings screen, which shows available readers, and SDK information. [getEnvironment](#getEnvironment) | String | Returns the current environment the SDK was initialized on. [getSdkVersion](#getSdkVersion) | String | Returns the current Mobile Payments SDK version. +[getSdkSettings](#getSdkSettings) | SdkSettings | Returns the SDK version, environment and security compliance version. +[getTrackingConsentState](#getTrackingConsentState) | TrackingConsentState | Returns the current analytics tracking consent state. +[updateTrackingConsent](#updateTrackingConsent) | void | Grants or denies analytics tracking consent. ### TapToPaySettings Namespace @@ -187,6 +308,34 @@ Returns the current Mobile Payments SDK version running. Note this is the versio * **On success**: returns a String with the current Mobile Payments SDK version. * **On failure**: Not applicable. +--- +### getSdkSettings + +Returns the SDK-level settings in a single object. + +* **On success**: returns a `SdkSettings` object. +* **On failure**: Not applicable. + +--- +### getTrackingConsentState + +Returns the current state of the consent granted for analytics and performance tracking. Consent only applies in jurisdictions where it is required; elsewhere the state is `NOT_REQUIRED`. + +* **On success**: returns a `TrackingConsentState` (`PENDING`, `GRANTED`, `DENIED`, `NOT_REQUIRED`). +* **On failure**: Not applicable. + +--- +### updateTrackingConsent + +Grants or denies consent for analytics and performance tracking. + +Parameter | Type | Description +--------- | ------ | ----------- +granted | Boolean | Whether consent is granted. + +* **On success**: the consent state is updated. +* **On failure**: Not applicable. + --- ### TapToPaySettings Methods @@ -253,6 +402,40 @@ sourceType | SourceType | The source type for this payment. tipMoney | Money | The amount designated as a tip, in addition to amountMoney totalMoney | Money | The total money for the payment, including amountMoney and tipMoney. updatedAt | Date | Timestamp of when the payment was last updated. +paymentType | PaymentType | `ONLINE` or `OFFLINE`. +status | PaymentStatus \| OfflinePaymentStatus | The payment status. +cardDetails | CardPaymentDetails \| OfflineCardPaymentDetails | Card details. Only populated when `sourceType` is `CARD`. +customerId | String | The ID of the customer associated with the payment (online payments). +note | String | The note attached to the payment (online payments). +teamMemberId | String | The ID of the team member associated with the payment (online payments). +cashDetails | CashPaymentDetails | Cash details. Only populated when `sourceType` is `CASH`. +receiptNumber | String | The receipt number (Android only). +receiptUrl | String | The URL of the receipt (Android only). +statementDescription | String | The statement description (Android only). +capabilities | Capabilities | The actions that can still be performed on the payment (Android only). +processingFee | PaymentProcessingFee[] | The processing fees (Android only). +localId | String | The local ID of an offline payment. +uploadedAt | Date | When an offline payment was uploaded. + +--- +### SdkSettings + +Field | Type | Description +----------------- | ----------------------- | -------------------- +sdkVersion | String | The Mobile Payments SDK version. +sdkEnvironment | Environment | `PRODUCTION` or `SANDBOX`. +securityComplianceVersion | String | The security compliance version of the SDK. + +--- +### IdempotencyKeyData + +Android only. Returned by `getAllIdempotencyKeys`. + +Field | Type | Description +----------------- | ----------------------- | -------------------- +idempotencyKey | String | The idempotency key. +paymentAttemptId | String | The payment attempt it belongs to. +updatedAt | Date | When the key was last updated. --- diff --git a/ios/Extensions.swift b/ios/Extensions.swift index f5fee0e..b545b55 100644 --- a/ios/Extensions.swift +++ b/ios/Extensions.swift @@ -90,7 +90,9 @@ extension ReaderFirmwareInfo { return [ "failureReason" : failureReason?.localizedDescription ?? NSNull(), "updatePercentage" : updatePercentage, - "version" : version + "version" : version, + "updateStatus" : updateStatus.toName(), + "updateTime" : updateTime?.ISO8601Format() ?? NSNull() ] } } @@ -190,3 +192,47 @@ extension TimeOfDay { ] } } + +extension CardInputMethods { + func toCardEntryMethodList() -> NSArray { + var result: [String] = [] + if contains(.chip) { result.append("EMV") } + if contains(.contactless) { result.append("CONTACTLESS") } + if contains(.swipe) { result.append("SWIPED") } + return result as NSArray + } +} + +extension FirmwareUpdateStatus { + func toName() -> String { + return switch self { + case FirmwareUpdateStatus.none: + "NONE" + case .pending: + "PENDING" + case .inProgress: + "IN_PROGRESS" + case .failed: + "FAILED" + default: + "NONE" + } + } +} + +extension ReaderConnectionType { + func toName() -> String { + return switch self { + case .usb: + "USB" + case .bluetooth: + "BLUETOOTH" + case .audio: + "AUDIO" + case .embedded: + "EMBEDDED" + default: + "UNKNOWN" + } + } +} diff --git a/ios/Mappers.swift b/ios/Mappers.swift index f68b8df..8e80bd8 100644 --- a/ios/Mappers.swift +++ b/ios/Mappers.swift @@ -122,7 +122,8 @@ class Mappers { } class func mapToDictionary(payment: Payment) -> NSDictionary { - return [ + let result = NSMutableDictionary() + result.addEntries(from: [ "amountMoney": Mappers.mapToDictionary(money: payment.amountMoney) ?? NSNull(), "appFeeMoney": Mappers.mapToDictionary(money: payment.appFeeMoney) ?? NSNull(), "createdAt": payment.createdAt.ISO8601Format(), @@ -134,7 +135,29 @@ class Mappers { "tipMoney": Mappers.mapToDictionary(money: payment.tipMoney) ?? NSNull(), "totalMoney": Mappers.mapToDictionary(money: payment.totalMoney) ?? NSNull(), "updatedAt": payment.updatedAt.ISO8601Format(), - ] + "cashDetails": payment.cashDetails?.toMap() ?? NSNull(), + ] as [String: Any]) + + if let onlinePayment = payment as? OnlinePayment { + result.addEntries(from: [ + "paymentType": "ONLINE", + "status": onlinePayment.status.mapToString(), + "cardDetails": onlinePayment.cardDetails?.toMap() ?? NSNull(), + "customerId": onlinePayment.customerID ?? NSNull(), + "note": onlinePayment.note ?? NSNull(), + "teamMemberId": onlinePayment.teamMemberID ?? NSNull(), + ] as [String: Any]) + } else if let offlinePayment = payment as? OfflinePayment { + result.addEntries(from: [ + "paymentType": "OFFLINE", + "localId": offlinePayment.localID, + "status": offlinePayment.status.mapToString(), + "cardDetails": offlinePayment.cardDetails?.toMap() ?? NSNull(), + "uploadedAt": offlinePayment.uploadedAt?.ISO8601Format() ?? NSNull(), + ] as [String: Any]) + } + + return result } static func mapToDictionary(reader: ReaderInfo) -> NSDictionary { @@ -150,6 +173,8 @@ class Mappers { "name" : reader.name, "serialNumber" : reader.serialNumber ?? NSNull(), "status" : reader.statusInfo.toMap(), + "connectionType" : reader.connectionType.toName(), + "isRebootable" : reader.isRebootable, "supportedCardEntryMethods" : reader.supportedInputMethods.toList() ] } @@ -160,6 +185,8 @@ extension ReaderStatusInfo { return [ "status" : status.toName(), "readerUnavailableReason" : unavailableReasonInfo?.reason.toName() ?? NSNull(), + "title" : unavailableReasonInfo?.title ?? NSNull(), + "detail" : unavailableReasonInfo?.detail ?? NSNull(), ] } } @@ -266,3 +293,202 @@ enum PaymentPromptError: Error { case cannotCreatePrompt } +extension SquareMobilePaymentsSDK.Environment { + func mapToString() -> String { + switch self { + case .production: + return "PRODUCTION" + case .sandbox: + fallthrough + @unknown default: + return "SANDBOX" + } + } +} + +extension SquareMobilePaymentsSDK.TrackingConsentState { + func mapToString() -> String { + switch self { + case .granted: + return "GRANTED" + case .denied: + return "DENIED" + case .notRequired: + return "NOT_REQUIRED" + case .pending: + fallthrough + @unknown default: + return "PENDING" + } + } +} + +extension CashPaymentDetails { + func toMap() -> NSDictionary { + return [ + "buyerSuppliedMoney": Mappers.mapToDictionary(money: buyerSuppliedMoney) ?? NSNull(), + "changeBackMoney": Mappers.mapToDictionary(money: changeBackMoney) ?? NSNull() + ] + } +} + +extension CardSurchargeDetails { + func toMap() -> NSDictionary { + return [ + "cardSurchargeMoney": Mappers.mapToDictionary(money: cardSurchargeMoney) ?? NSNull(), + "taxOnSurchargeMoney": Mappers.mapToDictionary(money: taxOnCardSurchargeMoney) ?? NSNull() + ] + } +} + +extension Card { + func toMap() -> NSDictionary { + return [ + "brand": cardBrand.mapToString(), + "coBrand": coBrand.mapToString(), + "lastFourDigits": last4 ?? NSNull(), + "expirationMonth": expMonth, + "expirationYear": expYear, + "cardholderName": cardholderName ?? NSNull(), + "id": id ?? NSNull() + ] + } +} + +extension OfflineCard { + func toMap() -> NSDictionary { + return [ + "brand": cardBrand.mapToString(), + "lastFourDigits": last4 ?? NSNull(), + "cardholderName": cardholderName ?? NSNull(), + "id": id ?? NSNull() + ] + } +} + +extension CardPaymentDetails { + func toMap() -> NSDictionary { + return [ + "card": card?.toMap() ?? NSNull(), + "entryMethod": entryMethod.mapToString(), + "status": status.mapToString(), + "applicationIdentifier": applicationIdentifier ?? NSNull(), + "applicationName": applicationName ?? NSNull(), + "authorizationCode": authResultCode ?? NSNull(), + "appliedCardSurchargeDetails": appliedCardSurchargeDetails?.toMap() ?? NSNull() + ] + } +} + +extension OfflineCardPaymentDetails { + func toMap() -> NSDictionary { + return [ + "card": card?.toMap() ?? NSNull(), + "entryMethod": entryMethod.mapToString(), + "applicationIdentifier": applicationIdentifier ?? NSNull(), + "applicationName": applicationName ?? NSNull() + ] + } +} + +extension CardBrand { + func mapToString() -> String { + switch self { + case .visa: return "VISA" + case .mastercard: return "MASTERCARD" + case .americanExpress: return "AMERICAN_EXPRESS" + case .discover: return "DISCOVER" + case .discoverDiners: return "DISCOVER_DINERS" + case .jcb: return "JCB" + case .chinaUnionPay: return "CHINA_UNIONPAY" + case .squareGiftCard: return "SQUARE_GIFT_CARD" + case .squareCapitalCard: return "SQUARE_CAPITAL_CARD" + case .interac: return "INTERAC" + case .eftpos: return "EFTPOS" + case .felica: return "FELICA" + case .ebt: return "EBT" + case .otherBrand: return "OTHER_BRAND" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} + +extension CardCoBrand { + func mapToString() -> String { + switch self { + case .afterpay: return "AFTERPAY" + case CardCoBrand.none: return "NONE" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} + +extension CardEntryMethod { + func mapToString() -> String { + switch self { + case .keyed: return "KEYED" + case .swiped: return "SWIPED" + case .emv: return "EMV" + case .contactless: return "CONTACTLESS" + case .onFile: return "ON_FILE" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} + +extension CardPaymentStatus { + func mapToString() -> String { + switch self { + case .authorized: return "AUTHORIZED" + case .captured: return "CAPTURED" + case .voided: return "VOIDED" + case .failed: return "FAILED" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} + +extension PaymentStatus { + func mapToString() -> String { + switch self { + case .approved: return "APPROVED" + case .completed: return "COMPLETED" + case .canceled: return "CANCELED" + case .failed: return "FAILED" + case .initialized: return "INITIALIZED" + case .pending: return "PENDING" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} + +extension OfflineStatus { + func mapToString() -> String { + switch self { + case .queued: return "QUEUED" + case .uploaded: return "UPLOADED" + case .failedToUpload: return "FAILED_TO_UPLOAD" + case .failedToProcess: return "FAILED_TO_PROCESS" + case .processed: return "PROCESSED" + case .unknown: + fallthrough + @unknown default: + return "UNKNOWN" + } + } +} diff --git a/ios/MobilePaymentsSdkReactNative.mm b/ios/MobilePaymentsSdkReactNative.mm index 2aeed48..bffd7ba 100644 --- a/ios/MobilePaymentsSdkReactNative.mm +++ b/ios/MobilePaymentsSdkReactNative.mm @@ -30,6 +30,16 @@ @interface RCT_EXTERN_MODULE(MobilePaymentsSdkReactNative, NSObject) RCT_EXTERN_METHOD(closeSettings:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getSdkSettings:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getTrackingConsentState:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(updateTrackingConsent:(BOOL)granted + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(showMockReaderUI:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -77,6 +87,24 @@ @interface RCT_EXTERN_MODULE(MobilePaymentsSdkReactNative, NSObject) RCT_EXTERN_METHOD(getTotalStoredPaymentAmount:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(getIdempotencyKey:(NSString *)paymentAttemptId + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(isPaymentInProgress:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getAvailableCardEntryMethods:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(addAvailableCardEntryMethodsCallback:(NSString *)refId + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(removeAvailableCardEntryMethodsCallback:(NSString *)refId + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(getReaders:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) @@ -100,6 +128,14 @@ @interface RCT_EXTERN_MODULE(MobilePaymentsSdkReactNative, NSObject) withResolve:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(setPreferredFirmwareUpdateTime:(NSDictionary *)time + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(setReducedChargingModeEnabled:(BOOL)enabled + withResolve:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + RCT_EXTERN_METHOD(isPairingInProgress:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) diff --git a/ios/MobilePaymentsSdkReactNative.swift b/ios/MobilePaymentsSdkReactNative.swift index 288e1e0..2cd80c1 100644 --- a/ios/MobilePaymentsSdkReactNative.swift +++ b/ios/MobilePaymentsSdkReactNative.swift @@ -16,13 +16,14 @@ class MobilePaymentsSdkReactNative: RCTEventEmitter { private var paymentHandle: PaymentHandle? private var readersObservers: [String: ReaderObserver] = [:] + private var cardInputMethodsObservers: [String: AvailableCardInputMethodsObserver] = [:] private var pairingResolveBlock: RCTPromiseResolveBlock? private var pairingRejectBlock: RCTPromiseRejectBlock? private var pairingHandler: PairingHandle? /// We use notifications to propagate authorization status changes and reader changes override func supportedEvents() -> [String]! { - return ["AuthorizationStatusChange", "ReaderChanged"] + return ["AuthorizationStatusChange", "ReaderChanged", "AvailableCardEntryMethodsChanged"] } /// Authorize: https://developer.squareup.com/docs/mobile-payments-sdk/ios/configure-authorize @@ -128,6 +129,27 @@ class MobilePaymentsSdkReactNative: RCTEventEmitter { } } + @objc(getSdkSettings:withRejecter:) + func getSdkSettings(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + let settings = mobilePaymentsSDK.settingsManager.sdkSettings + resolve([ + "sdkVersion": settings.version, + "sdkEnvironment": settings.environment.mapToString(), + "securityComplianceVersion": settings.securityComplianceVersion, + ] as NSDictionary) + } + + @objc(getTrackingConsentState:withRejecter:) + func getTrackingConsentState(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + resolve(mobilePaymentsSDK.settingsManager.trackingConsentState.mapToString()) + } + + @objc(updateTrackingConsent:withResolve:withRejecter:) + func updateTrackingConsent(granted: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + mobilePaymentsSDK.settingsManager.updateTrackingConsent(withGranted: granted) + resolve(NSNull()) + } + @objc(getEnvironment:withRejecter:) func getEnvironment(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { switch(mobilePaymentsSDK.settingsManager.sdkSettings.environment) { @@ -400,6 +422,39 @@ class MobilePaymentsSdkReactNative: RCTEventEmitter { } + @objc(getIdempotencyKey:withResolve:withRejecter:) + func getIdempotencyKey(paymentAttemptId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + let key = mobilePaymentsSDK.paymentManager.getIdempotencyKey(withPaymentAttemptId: paymentAttemptId) + resolve(key ?? NSNull()) + } + + @objc(isPaymentInProgress:withRejecter:) + func isPaymentInProgress(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + resolve(mobilePaymentsSDK.paymentManager.currentPaymentHandle != nil) + } + + @objc(getAvailableCardEntryMethods:withRejecter:) + func getAvailableCardEntryMethods(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + resolve(mobilePaymentsSDK.paymentManager.availableCardInputMethods.toCardEntryMethodList()) + } + + @objc(addAvailableCardEntryMethodsCallback:withResolve:withRejecter:) + func addAvailableCardEntryMethodsCallback(refId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + let observer = MobilePaymentsCardInputMethodsObserver(emitter: self) + mobilePaymentsSDK.paymentManager.add(observer) + cardInputMethodsObservers[refId] = observer + resolve(refId) + } + + @objc(removeAvailableCardEntryMethodsCallback:withResolve:withRejecter:) + func removeAvailableCardEntryMethodsCallback(refId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + if let observer = cardInputMethodsObservers[refId] { + mobilePaymentsSDK.paymentManager.remove(observer) + cardInputMethodsObservers.removeValue(forKey: refId) + } + resolve(NSNull()) + } + @objc(getReaders:withRejecter:) func getReaders(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { let readers = mobilePaymentsSDK.readerManager.readers @@ -494,6 +549,28 @@ class MobilePaymentsSdkReactNative: RCTEventEmitter { resolve(NSNull()) } + @objc(setPreferredFirmwareUpdateTime:withResolve:withRejecter:) + func setPreferredFirmwareUpdateTime(time: [String: Any]?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + guard let time else { + mobilePaymentsSDK.readerManager.readerSettings.preferredFirmwareUpdateTime = nil + resolve(NSNull()) + return + } + guard let hour = time["hour"] as? Int, let minute = time["minute"] as? Int, + let timeOfDay = TimeOfDay(hour: hour, minute: minute) else { + reject("INVALID_TIME_OF_DAY", "hour and minute are required and must form a valid time of day", nil) + return + } + mobilePaymentsSDK.readerManager.readerSettings.preferredFirmwareUpdateTime = timeOfDay + resolve(NSNull()) + } + + @objc(setReducedChargingModeEnabled:withResolve:withRejecter:) + func setReducedChargingModeEnabled(enabled: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + mobilePaymentsSDK.readerManager.readerSettings.reducedChargingModeEnabled = enabled + resolve(NSNull()) + } + @objc(isPairingInProgress:withRejecter:) func isPairingInProgress(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { resolve(mobilePaymentsSDK.readerManager.isPairingInProgress) @@ -547,6 +624,21 @@ class MobilePaymentsSdkReactNative: RCTEventEmitter { } } +class MobilePaymentsCardInputMethodsObserver: AvailableCardInputMethodsObserver { + let emitter: RCTEventEmitter + + init(emitter: RCTEventEmitter) { + self.emitter = emitter + } + + func availableCardInputMethodsDidChange(_ cardInputMethods: CardInputMethods) { + emitter.sendEvent( + withName: "AvailableCardEntryMethodsChanged", + body: ["methods": cardInputMethods.toCardEntryMethodList()] + ) + } +} + class MobilePaymentsReaderObserver: ReaderObserver { let refId: String let emitter: RCTEventEmitter diff --git a/src/internal/events.ts b/src/internal/events.ts new file mode 100644 index 0000000..3253be8 --- /dev/null +++ b/src/internal/events.ts @@ -0,0 +1,10 @@ +import { NativeEventEmitter } from 'react-native'; +import MobilePaymentsSdkReactNative from '../base_sdk'; + +export const nativeEventEmitter = new NativeEventEmitter( + MobilePaymentsSdkReactNative +); + +export const generateUUID = (): string => { + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; +}; diff --git a/src/managers/payment.ts b/src/managers/payment.ts index 0420066..bf79684 100644 --- a/src/managers/payment.ts +++ b/src/managers/payment.ts @@ -1,5 +1,9 @@ import MobilePaymentsSdkReactNative from '../base_sdk'; +import { Platform } from 'react-native'; +import { generateUUID, nativeEventEmitter } from '../internal/events'; +import type { CardEntryMethod } from '../models/enums'; import type { + IdempotencyKeyData, Money, OfflinePayment, Payment, @@ -18,6 +22,56 @@ export const startPayment = ( ); }; +export const getIdempotencyKey = ( + paymentAttemptId: string +): Promise => { + return MobilePaymentsSdkReactNative.getIdempotencyKey(paymentAttemptId); +}; + +export const isPaymentInProgress = (): Promise => { + return MobilePaymentsSdkReactNative.isPaymentInProgress(); +}; + +export const getAllIdempotencyKeys = (): Promise => { + return Platform.select({ + android: () => MobilePaymentsSdkReactNative.getAllIdempotencyKeys(), + ios: () => + Promise.reject(new Error('This feature is only available on Android.')), + })!(); +}; + +export const completePayment = (paymentId: string): Promise => { + return Platform.select({ + android: () => MobilePaymentsSdkReactNative.completePayment(paymentId), + ios: () => + Promise.reject(new Error('This feature is only available on Android.')), + })!(); +}; + +export const getAvailableCardEntryMethods = (): Promise => { + return MobilePaymentsSdkReactNative.getAvailableCardEntryMethods(); +}; + +export const setAvailableCardEntryMethodsCallback = ( + callback: (methods: CardEntryMethod[]) => void +): (() => void) => { + const refId = generateUUID(); + MobilePaymentsSdkReactNative.addAvailableCardEntryMethodsCallback(refId); + const subscription = nativeEventEmitter.addListener( + Platform.select({ + ios: 'AvailableCardEntryMethodsChanged', + android: `AvailableCardEntryMethodsChanged-${refId}`, + }) ?? 'AvailableCardEntryMethodsChanged', + (event: { methods: CardEntryMethod[] }) => { + callback(event.methods); + } + ); + return () => { + subscription.remove(); + MobilePaymentsSdkReactNative.removeAvailableCardEntryMethodsCallback(refId); + }; +}; + export const cancelPayment = (): Promise => { return MobilePaymentsSdkReactNative.cancelPayment(); }; diff --git a/src/managers/reader.ts b/src/managers/reader.ts index 01e686c..84f0843 100644 --- a/src/managers/reader.ts +++ b/src/managers/reader.ts @@ -8,6 +8,7 @@ import type { ReaderChangedEvent, ReaderInfo, ReaderSettings, + TimeOfDay, } from '../models/objects'; import type { RetryConnectionResult } from '../models/enums'; @@ -44,6 +45,18 @@ export const rebootReader = (id: string): Promise => { })!(); }; +export const setPreferredFirmwareUpdateTime = ( + time: TimeOfDay | null +): Promise => { + return MobilePaymentsSdkReactNative.setPreferredFirmwareUpdateTime(time); +}; + +export const setReducedChargingModeEnabled = ( + enabled: boolean +): Promise => { + return MobilePaymentsSdkReactNative.setReducedChargingModeEnabled(enabled); +}; + export const isPairingInProgress = (): Promise => { return MobilePaymentsSdkReactNative.isPairingInProgress(); }; diff --git a/src/managers/settings.ts b/src/managers/settings.ts index 0f1c9d9..775a9b0 100644 --- a/src/managers/settings.ts +++ b/src/managers/settings.ts @@ -1,5 +1,6 @@ import MobilePaymentsSdkReactNative from '../base_sdk'; -import type { Money } from '../models/objects'; +import type { Money, SdkSettings } from '../models/objects'; +import type { TrackingConsentState } from '../models/enums'; export const showSettings = (): Promise => { return MobilePaymentsSdkReactNative.showSettings(); @@ -21,6 +22,18 @@ export const closeSettings = (): Promise => { return MobilePaymentsSdkReactNative.closeSettings(); }; +export const getSdkSettings = (): Promise => { + return MobilePaymentsSdkReactNative.getSdkSettings(); +}; + +export const getTrackingConsentState = (): Promise => { + return MobilePaymentsSdkReactNative.getTrackingConsentState(); +}; + +export const updateTrackingConsent = (granted: boolean): Promise => { + return MobilePaymentsSdkReactNative.updateTrackingConsent(granted); +}; + export namespace PaymentSettings { export const isOfflineProcessingAllowed = (): Promise => { return MobilePaymentsSdkReactNative.isOfflineProcessingAllowed(); diff --git a/src/models/enums.ts b/src/models/enums.ts index c6ebc6c..e3964cc 100644 --- a/src/models/enums.ts +++ b/src/models/enums.ts @@ -266,3 +266,56 @@ export enum RetryConnectionResult { UNABLE_TO_RETRY = 'UNABLE_TO_RETRY', READER_NOT_FOUND = 'READER_NOT_FOUND', } + +export enum TrackingConsentState { + PENDING = 'PENDING', + GRANTED = 'GRANTED', + DENIED = 'DENIED', + NOT_REQUIRED = 'NOT_REQUIRED', +} + +export enum ReaderConnectionType { + USB = 'USB', + BLUETOOTH = 'BLUETOOTH', + AUDIO = 'AUDIO', + EMBEDDED = 'EMBEDDED', + UNKNOWN = 'UNKNOWN', +} + +export enum FirmwareUpdateStatus { + NONE = 'NONE', + PENDING = 'PENDING', + IN_PROGRESS = 'IN_PROGRESS', + FAILED = 'FAILED', +} + +export enum PaymentType { + ONLINE = 'ONLINE', + OFFLINE = 'OFFLINE', +} + +export enum VerificationMethod { + PIN = 'PIN', + SIGNATURE = 'SIGNATURE', + PIN_AND_SIGNATURE = 'PIN_AND_SIGNATURE', + ON_DEVICE = 'ON_DEVICE', + NONE = 'NONE', +} + +export enum VerificationResult { + SUCCESS = 'SUCCESS', + FAILURE = 'FAILURE', + UNKNOWN = 'UNKNOWN', +} + +export enum PaymentProcessingFeeType { + INITIAL = 'INITIAL', + ADJUSTMENT = 'ADJUSTMENT', +} + +export enum PaymentCapability { + EDIT_AMOUNT_UP = 'EDIT_AMOUNT_UP', + EDIT_AMOUNT_DOWN = 'EDIT_AMOUNT_DOWN', + EDIT_TIP_AMOUNT_UP = 'EDIT_TIP_AMOUNT_UP', + EDIT_TIP_AMOUNT_DOWN = 'EDIT_TIP_AMOUNT_DOWN', +} diff --git a/src/models/objects.ts b/src/models/objects.ts index 9587140..93d5468 100644 --- a/src/models/objects.ts +++ b/src/models/objects.ts @@ -8,11 +8,16 @@ import type { CurrencyCode, DelayAction, EntryMethod, + Environment, + FirmwareUpdateStatus, OfflinePaymentStatus, + PaymentProcessingFeeType, PaymentStatus, + PaymentType, PromptMode, ReaderBatteryLevel, ReaderChange, + ReaderConnectionType, ReaderConnectionFailureReason, ReaderConnectionFailureRecoverySuggestion, ReaderConnectionState, @@ -20,6 +25,8 @@ import type { ReaderModel, ReaderUnavailableReason, SourceType, + VerificationMethod, + VerificationResult, } from './enums'; export type Location = { @@ -27,6 +34,9 @@ export type Location = { currencyCode: String; name: String; mcc: String; + merchantId?: string; + businessName?: string; + cardProcessingActivated?: boolean; }; export type Money = { @@ -49,6 +59,14 @@ export type OnlinePayment = { tipMoney: Money; totalMoney: Money; updatedAt: Date; + paymentType?: PaymentType; + cashDetails?: CashPaymentDetails; + teamMemberId?: string; + capabilities?: Capabilities; + processingFee?: PaymentProcessingFee[]; + receiptNumber?: string; + receiptUrl?: string; + statementDescription?: string; }; export type OfflinePayment = { @@ -67,6 +85,8 @@ export type OfflinePayment = { totalMoney: Money; updatedAt: Date; uploadedAt: Date; + paymentType?: PaymentType; + cashDetails?: CashPaymentDetails; }; export type PaymentParameters = { @@ -103,6 +123,13 @@ export type Card = { expirationYear?: Number; id: String; lastFourDigits: String; + bin?: string; +}; + +export type CardSurchargeDetails = { + cardSurchargeMoney: Money; + taxOnSurchargeMoney?: Money; + totalSurchargeMoney?: Money; }; export type CardPaymentDetails = { @@ -112,6 +139,24 @@ export type CardPaymentDetails = { card: Card; entryMethod: EntryMethod; status: CardPaymentStatus; + appliedCardSurchargeDetails?: CardSurchargeDetails; + verificationMethod?: VerificationMethod; + verificationResults?: VerificationResult; +}; + +export type CashPaymentDetails = { + buyerSuppliedMoney?: Money; + changeBackMoney?: Money; +}; + +export type Capabilities = { + allCapabilities: string[]; +}; + +export type PaymentProcessingFee = { + effectiveAt: Date; + type: PaymentProcessingFeeType; + amountMoney: Money; }; export type OfflineCardPaymentDetails = { @@ -144,6 +189,20 @@ export type Payment = { tipMoney: Money; totalMoney: Money; updatedAt: Date; + paymentType?: PaymentType; + status?: PaymentStatus | OfflinePaymentStatus; + cardDetails?: CardPaymentDetails | OfflineCardPaymentDetails; + customerId?: string; + note?: string; + teamMemberId?: string; + cashDetails?: CashPaymentDetails; + capabilities?: Capabilities; + processingFee?: PaymentProcessingFee[]; + receiptNumber?: string; + receiptUrl?: string; + statementDescription?: string; + localId?: string; + uploadedAt?: Date; }; export type ReaderBatteryStatus = { @@ -168,11 +227,15 @@ export type ReaderFirmwareInfo = { failureReason?: String; updatePercentage: Number; version: String; + updateStatus?: FirmwareUpdateStatus; + updateTime?: Date; }; export type ReaderStatus = { status: ReaderInternalStatus; readerUnavailableReason?: ReaderUnavailableReason; + title?: string; + detail?: string; }; export type ReaderInfo = { @@ -188,6 +251,8 @@ export type ReaderInfo = { cardInsertionStatus?: CardInsertionStatus; firmwareInfo?: ReaderFirmwareInfo; isConnectionRetryable?: Boolean; + connectionType?: ReaderConnectionType; + isRebootable?: boolean; }; export type ReaderChangedEvent = { @@ -205,3 +270,15 @@ export type ReaderSettings = { isReducedChargingModeEnabled: boolean; preferredFirmwareUpdateTime?: TimeOfDay | null; }; + +export type SdkSettings = { + sdkVersion: string; + sdkEnvironment: Environment; + securityComplianceVersion: string; +}; + +export type IdempotencyKeyData = { + idempotencyKey: string; + paymentAttemptId: string; + updatedAt: Date; +};