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 {
+
@@ -315,6 +330,12 @@ export class CheckoutPageComponent extends Component {
+
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,