[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

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

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;

    • 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

[ { "errors": [ { "locations": [ { "line": 2, "column": 3 } ], "message": "General external validation error", "path": [ "commitOrder" ], "extensions": { "code": "PAYMENT.2.001", "rbiErrorCode": "2.001", "rbiErrorDomain": "PAYMENT", "statusCode": 500, "statusCategory": "5XX" } } ], "data": null } ]

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

Exported Diagram Image

image-20240828-101328.png

Diagram ZenUML

Exported Diagram Image

image-20240828-101501.png

Diagram ZenUML

 

Error Messages

Paycomet

Key

Description

Modal Title

Modal Message

Modal CTA

Screen

Paycomet

Key

Description

Modal Title

Modal Message

Modal CTA

Screen

102

error.PAYMENT.2.001

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

error.PAYMENT.2.100

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

error.PAYMENT.2.100.001

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

error.PAYMENT.2.101.001

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

error.PAYMENT.2.101.002

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

error.PAYMENT.2.102.002

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

error.PAYMENT.2.102.003

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.

(30min for Paycomet)

"Go Back and Try Again Later”

 

1196

PAYMENT.2.102.005

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

/** * @deprecated This function will be removed in future versions. * Use `<NEW_METHOD_NAME>` instead. */ const mapErrorsWithCodes = filterMap<GQLError, NonNullableObject<GQLError>, IGQLParsedErrorCode>( err => !!err.extensions?.code, err => ({ errorCode: err.extensions.code, message: err.message }) as IGQLParsedErrorCode );
parseGraphQLErrorCodes
  • path: intl-whitelabel-app/workspaces/frontend/src/utils/errors/index.ts

/** * @deprecated This function will be removed in future versions. * Use `NEW_METHOD_NAME` instead. */ export function parseGraphQLErrorCodes(error: GraphQLError | ApolloError): IGQLParsedErrorCode[] { if (isApolloError(error)) { return mapErrorsWithCodes(error.graphQLErrors); } return mapErrorsWithCodes(error.originalError || []); }

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 the reverse 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 first PAYMENT.2.102.001, as this item does not exist, the method will look for the next valid PAYMENT.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 the getRBIErrorTranslation method will return the Translation 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 first PAYMENT.4.008, as this item does not exist, the method will look for the next valid PAYMENT.4, but there isn’t too, so the method will search for the next PAYMENT 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;

    • 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 to optional;

  • 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 to optional:

    • Change the interface IErrorModal to optional and create a new attribute rbiError:

recordRBIErrorEvents

This method will send the new error events to mParticle/Amplitude;

The events will be:

event

Name

Value

Name

Value

name

ERROR (CustomEventNames.ERROR)

type

Other = 8 (EventTypes.Other)

attributes

Name

Value

Name

Value

'Error Code'

PAYMENT.2.001

Name

CVC2 block incorrect

Path

/cart/payment

'Response Title' (modal title)

title: Uh oh!

'Response Description' (modal description - User-friendly Messages)

description: The CVC number is incorrect. Please look carefully at your card details. This number is 3 figures and is usually on the back of your card. If the charge has been pre-authorized on your card, we will reimburse you within a maximum period of seven days.

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 the openErrorDialog

parseErrorModal
  • We will create a new parseErrorModal method to receive the existing if code block inside the openErrorDialog

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 and errorEventMessage will be undefined:

  • 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:

Call-outs