Skip to content

Commit

Permalink
Error handling improvements
Browse files Browse the repository at this point in the history
- Added `Failure` and `ErrorDetails` error objects
- Added convenience method `mapUserInfoToFailure` to extract data from a map into a Failure object
- Add Throwables and Error details to the errors in `promise.reject` calls in the Kotlin bridge code wherever possible
  • Loading branch information
Armaxis authored and plinio-square committed Jan 22, 2025
1 parent 03bc8f3 commit f527d9a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat
promise.resolve("Authorized with token: $accessToken and location: $locationId")

is Failure ->
promise.reject("AUTHENTICATION_ERROR", result.errorMessage)
promise.reject("AUTHENTICATION_ERROR", result.errorMessage, result.toErrorMap())
}
}
}
Expand Down Expand Up @@ -103,18 +103,8 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat
val settingsManager = MobilePaymentsSdk.settingsManager()
settingsManager.showSettings { result ->
when (result) {
is Success -> {
// Ensure that the value can be resolved correctly
promise.resolve("Settings closed successfully")
}

is Failure -> {
// Handle failure
promise.reject(
result.errorCode.toString(),
result.errorMessage
)
}
is Success -> promise.resolve("Settings closed successfully")
is Failure -> promise.reject("SETTINGS_ERROR", result.errorMessage, result.toErrorMap())
}
}
}
Expand Down Expand Up @@ -173,14 +163,14 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat
val parsedPaymentParameters = try {
paymentParameters.readPaymentParameters()
} catch (e: IllegalArgumentException) {
promise.reject("INVALID_PAYMENT_PARAMETERS", e.message)
promise.reject("INVALID_PAYMENT_PARAMETERS", e.message, e)
return
}

val parsedPromptParameters = try {
promptParameters.readPromptParameters()
} catch (e: IllegalArgumentException) {
promise.reject("INVALID_PAYMENT_PROMPT", e.message)
promise.reject("INVALID_PAYMENT_PROMPT", e.message, e)
return
}

Expand All @@ -196,8 +186,8 @@ class MobilePaymentsSdkReactNativeModule(private val reactContext: ReactApplicat
) { result ->
paymentHandle = null
when (result) {
is Failure -> promise.reject("PAYMENT_FAILED", result.toErrorMap())
is Success -> promise.resolve(result.value.toPaymentMap())
is Failure -> promise.reject("PAYMENT_FAILED", result.errorMessage, result.toErrorMap())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,12 @@ fun <S, C> Failure<S, C>.toErrorMap(): WritableMap = WritableNativeMap().apply {
putString("errorMessage", errorMessage)
putArray("details", WritableNativeArray().apply {
details.forEach {
val detailsString = "ErrorDetails[Category: ${it.category}, Code: ${it.code}, " +
"Detail: ${it.detail}, Field: ${it.field}]"
pushString(detailsString)
pushMap(WritableNativeMap().apply {
putString("category", it.category)
putString("code", it.code)
putString("detail", it.detail)
putString("field", it.field)
})
}
})
putString("debugCode", debugCode)
Expand Down
5 changes: 4 additions & 1 deletion example/src/Screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
showMockReaderUI,
showSettings,
startPayment,
mapUserInfoToFailure,
type PaymentParameters,
type PromptParameters,
} from 'mobile-payments-sdk-react-native';
Expand Down Expand Up @@ -67,7 +68,9 @@ const HomeView = () => {
const payment = await startPayment(paymentParameters, promptParameters);
console.log('Payment successful:', payment);
} catch (error) {
console.log('Payment error:', JSON.stringify(error.userInfo));
// convert the error.userInfo into a Failure object
const failure: Failure = mapUserInfoToFailure(error.userInfo);
console.log('Payment error:', JSON.stringify(failure));
}
};

Expand Down
60 changes: 60 additions & 0 deletions src/models/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,63 @@ export enum ReaderPairingError {
TIMED_OUT,
UPDATE_REQUIRED,
}

// Corresponds to the ErrorDetails inside the Failure object in the SDK
// Android: https://square.github.io/mobile-payments-sdk-android/-mobile%20-payments%20-s-d-k%20-android%20-technical%20-reference/com.squareup.sdk.mobilepayments.core/-error-details/index.html
export type ErrorDetails = {
category: String;
code: String;
detail: String;
field: String;
};

// Corresponds to the Failure object in the SDK
// Android: https://square.github.io/mobile-payments-sdk-android/-mobile%20-payments%20-s-d-k%20-android%20-technical%20-reference/com.squareup.sdk.mobilepayments.core/-result/-failure/index.html
// iOS:
export type Failure = {
debugMessage: String;
debugCode: String;
details: [ErrorDetails];
errorMessage: String;
errorCode: String;
};

// Convenience function to map the error.userInfo object to a Failure object
// For example
// ```
// try {
// const payment = await startPayment(paymentParameters, promptParameters);
// ...
// } catch (error) {
// const failure: Failure = mapUserInfoToFailure(error.userInfo);
// console.log('Payment error:', JSON.stringify(failure));
// }
// ```
export const mapUserInfoToFailure = (userInfo: any): Failure => {
if (userInfo == null) {
return {
debugMessage: '',
debugCode: '',
details: [],
errorMessage: '',
errorCode: '',
};
}
let details: ErrorDetails[] = [];
if (Array.isArray(userInfo.details)) {
// check if details is an array
details = userInfo.details.map((detail: any) => ({
category: detail.category,
code: detail.code,
detail: detail.detail,
field: detail.field,
}));
}
return {
debugMessage: userInfo.debugMessage ? userInfo.debugMessage : '',
debugCode: userInfo.debugCode ? userInfo.debugCode : '',
details: details,
errorMessage: userInfo.errorMessage ? userInfo.errorMessage : '',
errorCode: userInfo.errorCode ? userInfo.errorCode : '',
};
};

0 comments on commit f527d9a

Please sign in to comment.