diff --git a/CHANGELOG.md b/CHANGELOG.md index 870f79b0..e2cacf6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern: ## Upcoming version 2018-11-XX +* [fix] Show Stripe error message on CheckoutPage if payment request fails because of Stripe error. + Also show error if payment amount is zero. + [#960](https://github.com/sharetribe/flex-template-web/pull/960) * [fix] Remove unused translation keys and update PasswordChangePage title [#959](https://github.com/sharetribe/flex-template-web/pull/959) diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js index a1d2597d..abbb57d5 100644 --- a/src/containers/CheckoutPage/CheckoutPage.js +++ b/src/containers/CheckoutPage/CheckoutPage.js @@ -16,6 +16,8 @@ import { isTransactionInitiateListingNotFoundError, isTransactionInitiateMissingStripeAccountError, isTransactionInitiateBookingTimeNotAvailableError, + isTransactionZeroPaymentError, + transactionInitiateOrderStripeErrors, } from '../../util/errors'; import { AvatarMedium, @@ -273,6 +275,7 @@ export class CheckoutPageComponent extends Component { const isBookingTimeNotAvailableError = isTransactionInitiateBookingTimeNotAvailableError( initiateOrderError ); + const stripeErrors = transactionInitiateOrderStripeErrors(initiateOrderError); let initiateOrderErrorMessage = null; @@ -288,6 +291,18 @@ export class CheckoutPageComponent extends Component {

); + } else if (!listingNotFound && stripeErrors && stripeErrors.length > 0) { + // NOTE: Error messages from Stripes are not part of translations. + // By default they are in English. + const stripeErrorsAsString = stripeErrors.join(', '); + initiateOrderErrorMessage = ( +

+ +

+ ); } else if (!listingNotFound && initiateOrderError) { initiateOrderErrorMessage = (

@@ -315,6 +330,12 @@ export class CheckoutPageComponent extends Component {

); + } else if (isTransactionZeroPaymentError(speculateTransactionError)) { + speculateErrorMessage = ( +

+ +

+ ); } else if (speculateTransactionError) { speculateErrorMessage = (

diff --git a/src/translations/en.json b/src/translations/en.json index d537de3d..1ebd6b19 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -73,6 +73,7 @@ "CheckoutPage.hostedBy": "Hosted by {name}", "CheckoutPage.initiateOrderAmountTooLow": "We are unable to process this payment since the payment amount is too low. Please contact the marketplace administrator.", "CheckoutPage.initiateOrderError": "Payment request failed. Please go back to {listingLink} and try again. If the error persists, try refreshing the page or contacting the marketplace administrator.", + "CheckoutPage.initiateOrderStripeError": "The payment processor gave the following errors: {stripeErrors}", "CheckoutPage.listingNotFoundError": "Unfortunately, the listing is no longer available.", "CheckoutPage.loadingData": "Loading checkout data…", "CheckoutPage.paymentInfo": "You'll only be charged if your request is accepted by the provider.", diff --git a/src/util/errors.js b/src/util/errors.js index 732d52bd..903bfddc 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -15,6 +15,7 @@ import { ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_CUSTOMER, ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_PROVIDER, ERROR_CODE_PAYMENT_FAILED, + ERROR_CODE_CHARGE_ZERO_PAYIN, ERROR_CODE_EMAIL_TAKEN, ERROR_CODE_EMAIL_NOT_FOUND, ERROR_CODE_EMAIL_NOT_VERIFIED, @@ -111,12 +112,21 @@ export const isTransactionInitiateMissingStripeAccountError = error => export const isTransactionInitiateBookingTimeNotAvailableError = error => hasErrorWithCode(error, ERROR_CODE_TRANSACTION_BOOKING_TIME_NOT_AVAILABLE); +/** + * Check if the given API error (from `sdk.transaction.initiate()` or + * `sdk.transaction.initiateSpeculative()`) is due to payment being zero. + */ +export const isTransactionZeroPaymentError = error => + hasErrorWithCode(error, ERROR_CODE_CHARGE_ZERO_PAYIN); + /** * Check if the given API error (from `sdk.transaction.initiate()`) is * due to the transaction total amount being too low for Stripe. */ export const isTransactionInitiateAmountTooLowError = error => { - return responseAPIErrors(error).some(apiError => { + const isZeroPayment = isTransactionZeroPaymentError(error); + + const tooLowAmount = errorAPIErrors(error).some(apiError => { const isPaymentFailedError = apiError.status === 402 && apiError.code === ERROR_CODE_PAYMENT_FAILED; let isAmountTooLow = false; @@ -134,6 +144,26 @@ export const isTransactionInitiateAmountTooLowError = error => { return isPaymentFailedError && isAmountTooLow; }); + + return isZeroPayment || tooLowAmount; +}; + +/** + * Check if the given API error (from `sdk.transaction.initiate()`) is + * due to other error in Stripe. + */ +export const transactionInitiateOrderStripeErrors = error => { + if (error) { + return errorAPIErrors(error).reduce((messages, apiError) => { + const isPaymentFailedError = + apiError.status === 402 && apiError.code === ERROR_CODE_PAYMENT_FAILED; + const hasStripeError = apiError && apiError.meta && apiError.meta.stripeMessage; + const stripeMessageMaybe = + isPaymentFailedError && hasStripeError ? [apiError.meta.stripeMessage] : []; + return [...messages, ...stripeMessageMaybe]; + }, []); + } + return null; }; /** diff --git a/src/util/types.js b/src/util/types.js index 2c595e4b..0d1f4d65 100644 --- a/src/util/types.js +++ b/src/util/types.js @@ -446,6 +446,8 @@ export const ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_PROVIDER = export const ERROR_CODE_TRANSACTION_BOOKING_TIME_NOT_AVAILABLE = 'transaction-booking-time-not-available'; export const ERROR_CODE_PAYMENT_FAILED = 'transaction-payment-failed'; +export const ERROR_CODE_CHARGE_ZERO_PAYIN = 'transaction-charge-zero-payin'; +export const ERROR_CODE_CHARGE_ZERO_PAYOUT = 'transaction-charge-zero-payout'; export const ERROR_CODE_EMAIL_TAKEN = 'email-taken'; export const ERROR_CODE_EMAIL_NOT_FOUND = 'email-not-found'; export const ERROR_CODE_EMAIL_NOT_VERIFIED = 'email-unverified'; @@ -463,6 +465,8 @@ const ERROR_CODES = [ ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_CUSTOMER, ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_PROVIDER, ERROR_CODE_PAYMENT_FAILED, + ERROR_CODE_CHARGE_ZERO_PAYIN, + ERROR_CODE_CHARGE_ZERO_PAYOUT, ERROR_CODE_EMAIL_TAKEN, ERROR_CODE_EMAIL_NOT_FOUND, ERROR_CODE_EMAIL_NOT_VERIFIED,