[Solution] Detailed payment error messages
Document Status | CLOSED |
---|---|
Closing date | 2024-09-02 |
Document Owner(s) | @de Sousa Santos, Rodrigo |
Reviewers | @Raphael Ferreira Gomes @Pereira, Filipa (Deactivated) @Hellen Rodrigues Magalhaes @Almeida, Gabriel @Szekely, Szabolcs @Felipe Rooke @Magdalena Dlugolecka @Stybel, Filip @Rodrigues da Silva, Manuel |
- 1 Potential Solutions
- 2 Proposed Solution
- 2.1 Context
- 2.2 Proposal
- 2.2.1 Description
- 2.2.1.1 Workflow
- 2.2.1 Description
- 2.3 Error Messages
- 2.3.1 Old Methods
- 2.3.1.1 mapErrorsWithCodes
- 2.3.1.2 parseGraphQLErrorCodes
- 2.3.2 New Methods
- 2.3.2.1 IRbiError
- 2.3.2.2 mapRBIErrors
- 2.3.2.3 parseGraphQLRBIError
- 2.3.2.4 isRBIError
- 2.3.2.5 extractRBIErrorCodes
- 2.3.2.6 getRBIErrorTranslation
- 2.3.2.7 fetchRBIErrorDetails
- 2.3.3 Other Changes
- 2.3.3.1 ErrorTranslation
- 2.3.3.2 IDialogProps
- 2.3.4 useErrorModal
- 2.3.4.1 Change types
- 2.3.4.2 recordRBIErrorEvents
- 2.3.4.2.1 event
- 2.3.4.2.2 attributes
- 2.3.4.3 openErrorDialog
- 2.3.4.4 parseErrorModal
- 2.3.4.5 ErrorDialogComponent
- 2.3.5 How to use the new error modal?
- 2.3.5.1 Open the modal
- 2.3.5.2 Send the event without opening the modal
- 2.3.6 POC
- 2.3.1 Old Methods
- 3 Potential Challenges
- 4 Cost
- 5 Configuration
- 6 Metrics
- 7 Delivery Plan
- 8 QA Plan
- 9 Call-outs
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 |
Authorization from different country than card issuer, not allowed | 1196 |
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: https://rbictg.atlassian.net/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
Error Messages
Paycomet | Key | Description | Modal Title | Modal Message | Modal CTA | Screen |
---|---|---|---|---|---|---|
102 |
| Operation not allowed for the credit card type | “Payment Declined” | “Your card does not support this payment type at the moment. Please, contact your bank or try another payment method.” | "Change Payment Method” |
|
130 |
| Represents an error related to credit/debit card transactions. | “Payment Declined" | “Your card does not support this payment type at the moment. Please, contact your bank or try another payment method." | "Change Payment Method” |
|
111 |
| The payment transaction was rejected because the user informed an invalid card CVV. | “Payment Declined" | “The payment transaction was declined. Please check the card name, card number, CVV, expire date and try again.” | "Review Card Details” |
|
115 |
| The payment transaction was rejected because card issuer could not validate card owner's identity. | “Payment Declined" | “The transaction was rejected because the card issuer could not verify the cardholder's identity. Try again and authorize the transaction with the card issuer in order to proceed” | "Go Back and Try Again” |
|
1367 |
| Represents an error when In-Flight offline payments are rejected. An example of occurrence is when the same card has several payments in "flight" at the time and one is finalized, the rest might be denied. | “Payment Failed” | “Sorry, we are unable to process this payment. Please verify the payment in your bank platform or use another payment method.” | "Go Back and Try Again” |
|
1192 |
| Represents an error when the user's IP address is not from an allowed country. Ex.: Iberia payments only allowed from Spain and Portugal. | “Payment Declined" | “It seems you're connecting from a different country, and we're unable to process your order. ” | "Go Back” |
|
1202 |
| Represents an error when the number of failed attempts from the same user has been exceeded. | “Order Attempts Exceeded" | “You have exceeded the number of attempts to place an order. Please try again in X minutes.” | "Go Back and Try Again Later” |
|
1196 |
| Authorization from different country than card issuer, not allowed | “Payment Declined” | “Unfortunately, payments from different country than card issuer are not allowed on this platform. Please use a card issued in the same country or change your payment method.” | “Change Payment Method” |
|
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 interface IRbiError
and new methods will be parseGraphQLRBIError
and parseGraphQLRBIError
:
IRbiError
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.
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:
And the
extractRBIErrorCodes
method returns the array: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: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:
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
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:
Change the type UseErrorDialogHook to create a new attribute
RecordRBIErrorType
:Change the interface
IUseErrorModalArgs
tooptional
:Change the interface
IErrorModal
tooptional
and create a new attributerbiError
:
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:
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
:
And change the
Dialog
calls:
How to use the new error modal?
After all these changes, when it’s needed to use the new error modal:
import the useErrorModal
:
Open the modal
Send the event without opening the modal
POC
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
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
QA Plan
You can verify the test plan on the page: