Document Status | CLOSED |
---|---|
Closing date | 2024-09-02 |
Document Owner(s) | |
Reviewers |
🛠️ Potential Solutions
Note: this section should be short and sweet, documenting notes about different solutions that were considered during ideation. Can just be a link to a whiteboard for example. Expected outcome: one solution is chosen and documented in more detail under Proposed Solution.
✅ Proposed Solution
Context
Currently, the way issues on our platform are presented in Amplitude occurs through error modal events. While this approach is functional, it has significant limitations as it focuses solely on the appearance of the modal, without providing a clear view of the true source of the problems. Error modals can represent both front-end and back-end failures, resulting from a variety of factors, such as configuration errors, RBI implementation, partner integrations, and user-induced errors. At present, there is no clear path to understand the fluctuations in error modals, which prevents the team from comprehending the friction points that users face on our platform.
To address these issues, a list of error codes has been proposed that should be documented and shared with the FZ team, enabling clear and standardized communication regarding the types of errors that may arise. Moreover, a collaborative effort between the front-end and back-end teams will be necessary to ensure that error codes are sent correctly and that the documentation is accessible and understandable. We will implement a protocol to send errors with an initial layer of granularity to mParticle, so they can be utilized in Amplitude, providing immediate insights into the causes of variations and enabling deeper analysis.
In other words:
Allow greater versatility to display errors;
Allow user-friendly messages;
Organized and centralized errors;
Provide a list of accessible errors;
Allow greater versatility in sending events to mParticle, showing or not the error modal;
More details:
Proposal
Description
The focus of the frontend (Whitelabel) is to receive the new error code and be able to generate the translation according to the error, displaying it in the modals of the Commit Order flow.
Additionally, it falls within the scope of the frontend to create a new event sending method for mParticle/Amplitude that enables sending these events either when the modal appears or independently, without the need to display the modal.
At this moment the focus will be just on PAYCOMET errors:
Erros description | Paycomet Error Code |
---|---|
Operation not allowed for the credit card type | 102 |
Card issuer could not validate card owner | 115 |
CVC2 block incorrect | 111 |
Operation not allowed for the credit card type | 130 |
Country (customer IP address) not allowed | 1192 |
Number of failed attempts (last 30 minutes) from the same ip address exceeded | 1202 |
When the same card has several payments in "flight" at the time that one is finalized, the rest are denied with this code. This restriction is for security | 1367 |
The backend will return to the frontend from CommitOrder the response with:
code: The new error code with Domains that will be translated to show the error description in the modal;
e.g.:
PAYMENT.2.001
,PAYMENT.2.100.001
,PAYMENT.4.006
OBS.: The number after the Domain (PAYMENT.2.001) is the severity, more details: /wiki/spaces/RWLAPPwiki/pages/4858741214
rbiErrorCode: The new error code without Domains;
e.g.:
2.001
,2.102.003
,4.004
rbiErrorDomain: Domain related to the flow
e.g.:
PAYMENT
,CHECKOUT
,CART
statusCode: Status code
e.g.:
500
,303
,404
statusCategory: Status category
e.g.:
5XX
,3XX
,4XX
Currently, there are two error handling methods: mapErrorsWithCodes
and parseGraphQLErrorCodes
, which return the IGQLParsedErrorCode
object used for all GraphQL errors.
We will need to implement new methods to handle the new error response without impacting the existing error flow. These new methods will be the main ones for handling GraphQL errors.
For example, the payment flow will use the new methods solely for the errors mentioned above, while all other methods will continue to use mapErrorsWithCodes
and parseGraphQLErrorCodes
.
After consolidating and validating the hypotheses regarding this feature, we will begin the migration to the new error model.
Workflow
Old Methods
Until that migration starts, we will deprecate the methods mapErrorsWithCodes
and parseGraphQLErrorCodes
to prevent the creation of new error flows using the "old" approach, encouraging the adoption of the new model.
In summary, the current error flows will function independently of the new error model.
For this, we will add a doc comment in the top methods:
mapErrorsWithCodes
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
parseGraphQLErrorCodes
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
New Methods
Creating new methods to validate the new errors is good because it will allow the validation of specific attributes (for example: err.extensions?.rbiErrorDomain
) without needing to use the feature flag or the optional creation.
How?
The new methods will be parseGraphQLRBIError
and parseGraphQLRBIError
:
mapRBIErrors
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
parseGraphQLRBIError
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
Looking especially for the mapRBIErrors
method:
The new error will have a unique attribute, which no error object within Whitelabel has had until now:
rbiErrorDomain
This attribute will be just in the new error model, that is, any error returned by the backend with this existing or populated field will mean that it must follow the new error flow and will have the specific interface of the new model.
But, for this feature there won't be a feature flag?
Not exactly, the backend has a feature flag to the feature.
Therefore, when the backend returns the response with this attribute, the frontend will know what flow follows.
Positive Points:
No need to create more than one feature flag to the same feature;
Use the effect of enabling the Feature Flag from the backend to the frontend;
Avoids errors of enabling a feature flag, for example, on the frontend and not enabling it on the backend, as has already happened in the case of the skip pre-auth feature flags.
Deprecating the old methods will prevent the creation of new error streams using the "old" approach.
Creating new methods will guarantee the segregation of the flows between old errors and new errors.
Negative points:
Method validation cannot change (
!!err.extensions?.rbiErrorDomain
) to ensure that the flow works;To use the new error model, we will need to create an error code on the backend first;
For some time, the coexistence of 2 error streams will be necessary for old errors and new errors;
OBS.: If necessary there is a feature flag on the frontend, we can add the validation on the method mapRBIErrors
.
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
In addition of creating the new methods mapRBIErrors and parseGraphQLRBIError, it will need to create the other methods:
isRBIError
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
This method will be created to validate if the error is an RBI error, using the class RBIError2 which will renamed to RBIError in the future;
extractRBIErrorCodes
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
As the error is DOMAIN.SEVERITY.CODE.ERROR, for the translation, it will be necessary to create a method to split the error and get the translation.
For example:
If the error is:
PAYMENT.2.102.001
, the split will return the array with:PAYMENT.2.102.001
PAYMENT.2.102
PAYMENT.2
PAYMENT
This array will be processed for the method
getRBIErrorTranslation
, which will be discussed in the below topic.OBS.: This method needs to add in the return a function
.reverse()
to translation correct because normally the.split()
will return an array:PAYMENT
,PAYMENT.2
,PAYMENT.2.102
,PAYMENT.2.102.001
, so thereverse
is necessary to get the translation of the most detailed (PAYMENT.2.102.001
) error to the least detailed (PAYMENT
).
getRBIErrorTranslation
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
This method calls the extractRBIErrorCodes and will get the Lokalise the correct translation.
For example:
Example 1:
If the translation file (Lokalise) is:
"error.PAYMENT.title": "Translation for the error error.PAYMENT.title", "error.PAYMENT.default": "Translation for the error.PAYMENT.default", "error.PAYMENT.2.001": "Translation for the error 2.001", "error.PAYMENT.2.002": "Translation for the error 2.002", "error.PAYMENT.2.003": "Translation for the error 2.003", "error.PAYMENT.2.004": "Translation for the error 2.004", "error.PAYMENT.2.100": "Translation for the error 2.100",
And the
extractRBIErrorCodes
method returns the array:['PAYMENT.2.102.001', 'PAYMENT.2.102', 'PAYMENT.2', 'PAYMENT']
The
getRBIErrorTranslation
will search firstPAYMENT.2.102.001
, as this item does not exist, the method will look for the next validPAYMENT.2.102
, In this example there is the translation"error.PAYMENT.2.102": "Translation for the error 2.102"
, as the translation was found, so thegetRBIErrorTranslation
method will return theTranslation for the error 2.102
.OBS.: These translation labels are an example;
The label “error” at the beginning of the Lokalise file and the parameters, will explained below topic:
Example 2:
Using the same Lokalise translation described above:
If the
extractRBIErrorCodes
method returns the array:['PAYMENT.4.008', 'PAYMENT.4', 'PAYMENT']
The
getRBIErrorTranslation
will search firstPAYMENT.4.008
, as this item does not exist, the method will look for the next validPAYMENT.4
, but there isn’t too, so the method will search for the nextPAYMENT
and there isn’t.In this example, will return the default translation,
"error.PAYMENT.default": "Translation for the error.PAYMENT.default"
.
fetchRBIErrorDetails
The last new method will be: fetchRBIErrorDetails
;
This method is responsible for orchestrating which label will need to be translated. If it will translate the modal title, description modal, etc.
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts
For example, if the translation needs to be the title, this method will call the getRBIErrorTranslation like this:
const title = getRBIErrorTranslation({ errorCode: rbiError.errorCode, errorTranslation: ErrorTranslation.TITLE, defaultTranslationId: `error.${rbiError.rbiErrorDomain}.${ErrorTranslation.TITLE}`, formatMessage, });
Parameters:
errorCode: The new error code with Domains that will be translated to show the error description in the modal;
e.g.:
PAYMENT.2.001
errorTranslation: If this translation is a title or description error, if it will be the description, it won’t add the errorTranslation, because the own error
PAYMENT.2.001
key will be the description;Then, we will need to create a new enum ErrorTranslation:
defaultTranslationId: What default key will need to translate if the error code won’t be found:
`error.PAYMENT.title`
for titles;`error.PAYMENT.default`
for descriptions;
formatMessage: Method used for Whitelabel to get the translation from Lokalise;
Other Changes
ErrorTranslation
Create a new enum to be used on fetchRBIErrorDetails
path:
intl-whitelabel-app/workspaces/frontend/src/utils/errors/types.ts
IDialogProps
Change the attribute
modalAppearanceEventMessage
tooptional
;path:
intl-whitelabel-app/workspaces/frontend/src/components/dialog/index.tsx
export interface IDialogProps extends IBaseProps { ... modalAppearanceEventMessage?: string; ... }
useErrorModal
The existing hook is the hook that processes the modal error component;
Path:
intl-whitelabel-app/workspaces/frontend/src/hooks/use-error-modal.tsx
For this feature, we will need to change this hook to accommodate the requirements to add to this doc;
Change the
openErrorDialog
method to receive the new error model;Create a new method
recordRbiError
to send the events to mParticle/Amplitude without open the modal;
Change types
Path:
intl-whitelabel-app/workspaces/frontend/src/hooks/use-error-modal.tsx
Create a new type:
export type RecordRBIErrorType = ({ rbiErrors }: { rbiErrors: RbiError2[] }) => void;
Change the type UseErrorDialogHook to create a new attribute
RecordRBIErrorType
:export type UseErrorDialogHook<P = any> = [ React.FC<Props<P>>, ModalCb, VoidFunction, recordRBIErrorType, ];
Change the interface
IUseErrorModalArgs
tooptional
:interface IUseErrorModalArgs<T> { ... modalAppearanceEventMessage?: string; }
Change the interface
IErrorModal
tooptional
and create a new attributerbiError
:export interface IErrorModal { ... message?: string | ReactNode; ... rbiError?: RbiError2[]; }
recordRBIErrorEvents
This method will send the new error events to mParticle/Amplitude;
The events will be:
event
Name | Value |
---|---|
|
|
|
|
attributes
Name | Value |
---|---|
|
|
|
|
|
|
| title: |
| description: |
If called to recordRBIErrorEvents
(Without Modal)
Won’t be sent the
'Response Title'
and'Response Description'
openErrorDialog
This method is responsible for opening the modal error;
Path:
intl-whitelabel-app/workspaces/frontend/src/hooks/use-error-modal.tsx
For the code to get more legible, we will need to refactor it:
We will create a new errorModal method to receive the existing
if
code block inside theopenErrorDialog
parseErrorModal
We will create a new
parseErrorModal
method to receive the existingif
code block inside theopenErrorDialog
ErrorDialogComponent
Path:
intl-whitelabel-app/workspaces/frontend/src/hooks/use-error-modal.tsx
To accommodate the changes, will need to change the component adding the get to recover the error:
const pendingRbiError = delve(pendingData as T, 'error', null);
This component calls the other component Dialog
and this sends its own event to mParticle/Amplitude . We will need to change this calling to not send the event from Dialog
and send the event independently.
Then, we will need to validate the error type:
If the error is the new error modal (RBIError), the
modalAppearanceEvent
anderrorEventMessage
will beundefined
:
const modalAppearanceEvent = isRBIError(pendingRbiError) ? undefined : modalAppearanceEventMessageOverride.current || modalAppearanceEventMessage; const errorEventMessage = isRBIError(pendingRbiError) ? undefined : errorEventMessageOverride.current;
And change the
Dialog
calls:<Dialog heading={title || heading} body={dialogBody} image={image} onDismiss={dismissDialog} actions={actions} modalAppearanceEventMessage={modalAppearanceEvent} errorEventMessage={errorEventMessage} aria-label="error" {...rest} />
How to use the new error model?
After all these changes, when it’s needed to use the new error model:
import the useErrorModal
:
const [ErrorDialog, openErrorDialog, recordRBIErrorEvents] = useErrorModal();
Open the modal
const errorCodeRBI = parseGraphQLRBIError(error); openErrorDialog({ rbiError: errorCodeRBI });
Send the event without opening the modal
const errorCodeRBI = parseGraphQLRBIError(error); recordRBIErrorEvents({ rbiErrors: errorCodeRBI });
Creating the RBI error on frontend
const errorCodeRBI = new RbiError2('Paypal modal error', 'PAYMENT', '2.000'); // with Modal openErrorDialog({ rbiError: errorCodeRBI }); // without modal recordRBIErrorEvents({ rbiErrors: errorCodeRBI });
POC
Message Description
Key | Description (Sent from backend) | Message Description (Modal description - User-friendly Messages) |
---|---|---|
| - | |
| - | |
| Operation not allowed for the credit card type | |
| General fraud error | |
| General system fraud error | |
| General validation error | |
| General card error | |
| CVC2 block incorrect | |
| General payment processing error | |
| Card issuer could not validate card owner | |
| When the same card has several payments in "flight" at the time that one is finalized, the rest are denied with this code. This restriction is for security. | |
| General payment restriction error | |
| Operation not allowed for the credit card type | |
| Country (customer IP address) not allowed | |
| Number of failed attempts (last 30 minutes) from the same ip address exceeded | |
| General PSP error | |
| General configuration error | |
| General connection error | |
| General database error | |
| General external connection error | |
| General external error | |
| General service error | |
| General payment process error |
More details on Github: Generic Errors, Paycomet Error Map, and PSP Error
Lokalise file:
Path:
intl-whitelabel-app/workspaces/frontend/src/state/translations/en.json
Potential Challenges
A potential challenge is:
Keeping updated sync of all translations (English, Spanish, Portuguese, etc);
Migrating the old error model to the new error model that will be made in the future, can be difficult due to the number of error flows that exist in Whitelabel.
💰 Cost
Describe any additional cost this solution may imply.
🎛️ Configuration
Include here any configuration required (Sanity, LaunchDarkly flags, etc.) required to enable the solution.
📈 Metrics
After the feature, will it be possible to look at the event attributes, will be able to verify an increase of x% of detailed errors compared to previous deployment events;
🗓️ Delivery Plan
Link to the task or list of all the tasks required to deliver the solution, along with its progress.
🧑🚒 QA Plan
You can verify the test plan on the page:
⚠️ Call-outs
Tip: Document here any improvement or discussion regarding the feature
Add Comment