Skip to content

Commit

Permalink
Merge pull request #1339 from sharetribe/fix-use-confirm-card-payment-fn
Browse files Browse the repository at this point in the history
Use confirmCardPayment instead of deprecated handleCardPayment
  • Loading branch information
OtterleyW authored Aug 11, 2020
2 parents 85374e0 + 754d37f commit 44e6cfe
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 60 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2020-XX-XX

- [fix] Use Stripe's `confirmCardPayment` function instead of deprecated `handleCardPayment` to
confirm PaymentIntent. In addition to the rename, the arguments passed to `handleCardPayment` are
sligthly different. Otherwise, these changes should not affect the behavior of the function.
[#1339](https://github.com/sharetribe/ftw-daily/pull/1339)

## [v6.1.1] 2020-07-21

- [fix] Fix config script for NodeJS v14.5.0
Expand Down
37 changes: 19 additions & 18 deletions src/containers/CheckoutPage/CheckoutPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
} from '../../components';
import { StripePaymentForm } from '../../forms';
import { isScrollingDisabled } from '../../ducks/UI.duck';
import { handleCardPayment, retrievePaymentIntent } from '../../ducks/stripe.duck';
import { confirmCardPayment, retrievePaymentIntent } from '../../ducks/stripe.duck';
import { savePaymentMethod } from '../../ducks/paymentMethods.duck';

import {
Expand Down Expand Up @@ -210,7 +210,7 @@ export class CheckoutPageComponent extends Component {
currentUser,
stripeCustomerFetched,
onInitiateOrder,
onHandleCardPayment,
onConfirmCardPayment,
onConfirmPayment,
onSendMessage,
onSavePaymentMethod,
Expand Down Expand Up @@ -255,7 +255,7 @@ export class CheckoutPageComponent extends Component {
};

// Step 2: pay using Stripe SDK
const fnHandleCardPayment = fnParams => {
const fnConfirmCardPayment = fnParams => {
// fnParams should be returned transaction entity

const order = ensureTransaction(fnParams);
Expand Down Expand Up @@ -288,8 +288,9 @@ export class CheckoutPageComponent extends Component {
const paymentParams =
selectedPaymentFlow !== USE_SAVED_CARD
? {
payment_method_data: {
payment_method: {
billing_details: billingDetails,
card: card,
},
}
: {};
Expand All @@ -303,12 +304,12 @@ export class CheckoutPageComponent extends Component {
};

// If paymentIntent status is not waiting user action,
// handleCardPayment has been called previously.
// confirmCardPayment has been called previously.
const hasPaymentIntentUserActionsDone =
paymentIntent && STRIPE_PI_USER_ACTIONS_DONE_STATUSES.includes(paymentIntent.status);
return hasPaymentIntentUserActionsDone
? Promise.resolve({ transactionId: order.id, paymentIntent })
: onHandleCardPayment(params);
: onConfirmCardPayment(params);
};

// Step 3: complete order by confirming payment to Marketplace API
Expand Down Expand Up @@ -347,13 +348,13 @@ export class CheckoutPageComponent extends Component {
// Here we create promise calls in sequence
// This is pretty much the same as:
// fnRequestPayment({...initialParams})
// .then(result => fnHandleCardPayment({...result}))
// .then(result => fnConfirmCardPayment({...result}))
// .then(result => fnConfirmPayment({...result}))
const applyAsync = (acc, val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
const handlePaymentIntentCreation = composeAsync(
fnRequestPayment,
fnHandleCardPayment,
fnConfirmCardPayment,
fnConfirmPayment,
fnSendMessage,
fnSavePaymentMethod
Expand All @@ -366,7 +367,7 @@ export class CheckoutPageComponent extends Component {

// Note: optionalPaymentParams contains Stripe paymentMethod,
// but that can also be passed on Step 2
// stripe.handleCardPayment(stripe, { payment_method: stripePaymentMethodId })
// stripe.confirmCardPayment(stripe, { payment_method: stripePaymentMethodId })
const optionalPaymentParams =
selectedPaymentFlow === USE_SAVED_CARD && hasDefaultPaymentMethod
? { paymentMethod: stripePaymentMethodId }
Expand Down Expand Up @@ -500,7 +501,7 @@ export class CheckoutPageComponent extends Component {
intl,
params,
currentUser,
handleCardPaymentError,
confirmCardPaymentError,
paymentIntent,
retrievePaymentIntentError,
stripeCustomerFetched,
Expand Down Expand Up @@ -738,7 +739,7 @@ export class CheckoutPageComponent extends Component {
: null;

// If paymentIntent status is not waiting user action,
// handleCardPayment has been called previously.
// confirmCardPayment has been called previously.
const hasPaymentIntentUserActionsDone =
paymentIntent && STRIPE_PI_USER_ACTIONS_DONE_STATUSES.includes(paymentIntent.status);

Expand Down Expand Up @@ -801,7 +802,7 @@ export class CheckoutPageComponent extends Component {
showInitialMessageInput={showInitialMessageInput}
initialValues={initalValuesForStripePayment}
initiateOrderError={initiateOrderError}
handleCardPaymentError={handleCardPaymentError}
confirmCardPaymentError={confirmCardPaymentError}
confirmPaymentError={confirmPaymentError}
hasHandledCardPayment={hasPaymentIntentUserActionsDone}
loadingData={!stripeCustomerFetched}
Expand Down Expand Up @@ -883,14 +884,14 @@ CheckoutPageComponent.propTypes = {
}).isRequired,
onConfirmPayment: func.isRequired,
onInitiateOrder: func.isRequired,
onHandleCardPayment: func.isRequired,
onConfirmCardPayment: func.isRequired,
onRetrievePaymentIntent: func.isRequired,
onSavePaymentMethod: func.isRequired,
onSendMessage: func.isRequired,
initiateOrderError: propTypes.error,
confirmPaymentError: propTypes.error,
// handleCardPaymentError comes from Stripe so that's why we can't expect it to be in a specific form
handleCardPaymentError: oneOfType([propTypes.error, object]),
// confirmCardPaymentError comes from Stripe so that's why we can't expect it to be in a specific form
confirmCardPaymentError: oneOfType([propTypes.error, object]),
paymentIntent: object,

// from connect
Expand Down Expand Up @@ -919,7 +920,7 @@ const mapStateToProps = state => {
confirmPaymentError,
} = state.CheckoutPage;
const { currentUser } = state.user;
const { handleCardPaymentError, paymentIntent, retrievePaymentIntentError } = state.stripe;
const { confirmCardPaymentError, paymentIntent, retrievePaymentIntentError } = state.stripe;
return {
scrollingDisabled: isScrollingDisabled(state),
currentUser,
Expand All @@ -932,7 +933,7 @@ const mapStateToProps = state => {
transaction,
listing,
initiateOrderError,
handleCardPaymentError,
confirmCardPaymentError,
confirmPaymentError,
paymentIntent,
retrievePaymentIntentError,
Expand All @@ -946,7 +947,7 @@ const mapDispatchToProps = dispatch => ({
fetchStripeCustomer: () => dispatch(stripeCustomer()),
onInitiateOrder: (params, transactionId) => dispatch(initiateOrder(params, transactionId)),
onRetrievePaymentIntent: params => dispatch(retrievePaymentIntent(params)),
onHandleCardPayment: params => dispatch(handleCardPayment(params)),
onConfirmCardPayment: params => dispatch(confirmCardPayment(params)),
onConfirmPayment: params => dispatch(confirmPayment(params)),
onSendMessage: params => dispatch(sendMessage(params)),
onSavePaymentMethod: (stripeCustomer, stripePaymentMethodId) =>
Expand Down
4 changes: 2 additions & 2 deletions src/containers/CheckoutPage/CheckoutPage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ describe('CheckoutPage', () => {
speculateTransactionInProgress: false,
scrollingDisabled: false,
onConfirmPayment: noop,
onHandleCardPayment: noop,
onConfirmCardPayment: noop,
onInitiateOrder: noop,
onRetrievePaymentIntent: noop,
onSavePaymentMethod: noop,
onSendMessage: noop,
handleCardPaymentInProgress: false,
confirmCardPaymentInProgress: false,
};
const tree = renderShallow(<CheckoutPageComponent {...props} />);
expect(tree).toMatchSnapshot();
Expand Down
44 changes: 22 additions & 22 deletions src/ducks/stripe.duck.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export const RETRIEVE_PAYMENT_INTENT_ERROR = 'app/stripe/RETRIEVE_PAYMENT_INTENT
// ================ Reducer ================ //

const initialState = {
handleCardPaymentInProgress: false,
handleCardPaymentError: null,
confirmCardPaymentInProgress: false,
confirmCardPaymentError: null,
handleCardSetupInProgress: false,
handleCardSetupError: null,
paymentIntent: null,
Expand Down Expand Up @@ -95,14 +95,14 @@ export default function reducer(state = initialState, action = {}) {
case HANDLE_CARD_PAYMENT_REQUEST:
return {
...state,
handleCardPaymentError: null,
handleCardPaymentInProgress: true,
confirmCardPaymentError: null,
confirmCardPaymentInProgress: true,
};
case HANDLE_CARD_PAYMENT_SUCCESS:
return { ...state, paymentIntent: payload, handleCardPaymentInProgress: false };
return { ...state, paymentIntent: payload, confirmCardPaymentInProgress: false };
case HANDLE_CARD_PAYMENT_ERROR:
console.error(payload);
return { ...state, handleCardPaymentError: payload, handleCardPaymentInProgress: false };
return { ...state, confirmCardPaymentError: payload, confirmCardPaymentInProgress: false };

case HANDLE_CARD_SETUP_REQUEST:
return {
Expand All @@ -119,8 +119,8 @@ export default function reducer(state = initialState, action = {}) {
case CLEAR_HANDLE_CARD_PAYMENT:
return {
...state,
handleCardPaymentInProgress: false,
handleCardPaymentError: null,
confirmCardPaymentInProgress: false,
confirmCardPaymentError: null,
paymentIntent: null,
};

Expand Down Expand Up @@ -151,16 +151,16 @@ export const stripeAccountClearError = () => ({
type: STRIPE_ACCOUNT_CLEAR_ERROR,
});

export const handleCardPaymentRequest = () => ({
export const confirmCardPaymentRequest = () => ({
type: HANDLE_CARD_PAYMENT_REQUEST,
});

export const handleCardPaymentSuccess = payload => ({
export const confirmCardPaymentSuccess = payload => ({
type: HANDLE_CARD_PAYMENT_SUCCESS,
payload,
});

export const handleCardPaymentError = payload => ({
export const confirmCardPaymentError = payload => ({
type: HANDLE_CARD_PAYMENT_ERROR,
payload,
error: true,
Expand Down Expand Up @@ -240,35 +240,35 @@ export const retrievePaymentIntent = params => dispatch => {
});
};

export const handleCardPayment = params => dispatch => {
export const confirmCardPayment = params => dispatch => {
// It's required to use the same instance of Stripe as where the card has been created
// so that's why Stripe needs to be passed here and we can't create a new instance.
const { stripe, card, paymentParams, stripePaymentIntentClientSecret } = params;
const { stripe, paymentParams, stripePaymentIntentClientSecret } = params;
const transactionId = params.orderId;

dispatch(handleCardPaymentRequest());
dispatch(confirmCardPaymentRequest());

// When using default payment method, card (aka Stripe Element) is not needed.
// We also set paymentParams.payment_method already in Flex API side,
// when request-payment transition is made - so there's no need for paymentParams
const args = card
? [stripePaymentIntentClientSecret, card, paymentParams]
// When using default payment method paymentParams.payment_method is
// already set Flex API side, when request-payment transition is made
// so there's no need for paymentParams
const args = paymentParams
? [stripePaymentIntentClientSecret, paymentParams]
: [stripePaymentIntentClientSecret];

return stripe
.handleCardPayment(...args)
.confirmCardPayment(...args)
.then(response => {
if (response.error) {
return Promise.reject(response);
} else {
dispatch(handleCardPaymentSuccess(response));
dispatch(confirmCardPaymentSuccess(response));
return { ...response, transactionId };
}
})
.catch(err => {
// Unwrap Stripe error.
const e = err.error || storableError(err);
dispatch(handleCardPaymentError(e));
dispatch(confirmCardPaymentError(e));

// Log error
const containsPaymentIntent = err.error && err.error.payment_intent;
Expand Down
20 changes: 10 additions & 10 deletions src/forms/StripePaymentForm/StripePaymentForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ const initialState = {
* Payment form that asks for credit card info using Stripe Elements.
*
* When the card is valid and the user submits the form, a request is
* sent to the Stripe API to handle payment. `stripe.handleCardPayment`
* sent to the Stripe API to handle payment. `stripe.confirmCardPayment`
* may ask more details from cardholder if 3D security steps are needed.
*
* See: https://stripe.com/docs/payments/payment-intents
Expand Down Expand Up @@ -321,7 +321,7 @@ class StripePaymentForm extends Component {
showInitialMessageInput,
intl,
initiateOrderError,
handleCardPaymentError,
confirmCardPaymentError,
confirmPaymentError,
invalid,
handleSubmit,
Expand All @@ -338,22 +338,22 @@ class StripePaymentForm extends Component {
const onetimePaymentNeedsAttention = !billingDetailsKnown && !this.state.cardValueValid;
const submitDisabled = invalid || onetimePaymentNeedsAttention || submitInProgress;
const hasCardError = this.state.error && !submitInProgress;
const hasPaymentErrors = handleCardPaymentError || confirmPaymentError;
const hasPaymentErrors = confirmCardPaymentError || confirmPaymentError;
const classes = classNames(rootClassName || css.root, className);
const cardClasses = classNames(css.card, {
[css.cardSuccess]: this.state.cardValueValid,
[css.cardError]: hasCardError,
});

// TODO: handleCardPayment can create all kinds of errors.
// TODO: confirmCardPayment can create all kinds of errors.
// Currently, we provide translation support for one:
// https://stripe.com/docs/error-codes
const piAuthenticationFailure = 'payment_intent_authentication_failure';
const paymentErrorMessage =
handleCardPaymentError && handleCardPaymentError.code === piAuthenticationFailure
? intl.formatMessage({ id: 'StripePaymentForm.handleCardPaymentError' })
: handleCardPaymentError
? handleCardPaymentError.message
confirmCardPaymentError && confirmCardPaymentError.code === piAuthenticationFailure
? intl.formatMessage({ id: 'StripePaymentForm.confirmCardPaymentError' })
: confirmCardPaymentError
? confirmCardPaymentError.message
: confirmPaymentError
? intl.formatMessage({ id: 'StripePaymentForm.confirmPaymentError' })
: intl.formatMessage({ id: 'StripePaymentForm.genericError' });
Expand Down Expand Up @@ -513,7 +513,7 @@ StripePaymentForm.defaultProps = {
hasHandledCardPayment: false,
defaultPaymentMethod: null,
initiateOrderError: null,
handleCardPaymentError: null,
confirmCardPaymentError: null,
confirmPaymentError: null,
};

Expand All @@ -523,7 +523,7 @@ StripePaymentForm.propTypes = {
inProgress: bool,
loadingData: bool,
initiateOrderError: object,
handleCardPaymentError: object,
confirmCardPaymentError: object,
confirmPaymentError: object,
formId: string.isRequired,
intl: intlShape.isRequired,
Expand Down
4 changes: 2 additions & 2 deletions src/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@
"PaymentMethodsForm.billingDetailsNamePlaceholder": "Name eingeben…",
"PaymentMethodsForm.paymentCardDetails": "Zahlungskartendaten",
"PaymentMethodsForm.genericError": "Zahlungsdaten konnten nicht verarbeitet werden. Bitte erneut versuchen.",
"PaymentMethodsForm.handleCardPaymentError": "Deine Zahlungsmethode konnte nicht authentifiziert werden. Bitte deine Authentifizierungsdetails prüfen und erneut versuchen.",
"PaymentMethodsForm.confirmCardPaymentError": "Deine Zahlungsmethode konnte nicht authentifiziert werden. Bitte deine Authentifizierungsdetails prüfen und erneut versuchen.",
"PaymentMethodsForm.infoText": "Ich authorisiere Saunatime, dem Finanzinstitut das meine Karte ausgegeben hat, Zahlungen von meinem Kartenkonto in Übereinstimmung mit den Bedingungen meines Vertrags mit Ihnen zu entnehmen.",
"PaymentMethodsForm.paymentHeading": "Zahlung",
"PaymentMethodsPage.heading": "Zahlungsmethoden",
Expand Down Expand Up @@ -812,7 +812,7 @@
"StripePaymentForm.billingDetailsNamePlaceholder": "Name eingeben…",
"StripePaymentForm.confirmPaymentError": "Die Zahlung ist erfolgt, aber wir konnten die Buchung nicht bestätigen. Bitte versuche, die Buchungsanfrage erneut zu bestätigen! Wenn die Buchung nicht rechtzeitigt bestätigt ist, wird die Zahlung vollständig erstattet.",
"StripePaymentForm.genericError": "Zahlungsinformationen konnten nicht verarbeitet werden. Bitte erneut versuchen.",
"StripePaymentForm.handleCardPaymentError": "Deine Zahlungsmethode konnte nicht authentifiziert werden. Bitte deine Authentifizierungsdetails prüfen und erneut versuchen.",
"StripePaymentForm.confirmCardPaymentError": "Deine Zahlungsmethode konnte nicht authentifiziert werden. Bitte deine Authentifizierungsdetails prüfen und erneut versuchen.",
"StripePaymentForm.messageHeading": "Nachricht",
"StripePaymentForm.messageLabel": "Hallo an deine(n) Gastgeber(in) {messageOptionalText} sagen",
"StripePaymentForm.messageOptionalText": "• optional",
Expand Down
5 changes: 3 additions & 2 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"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 data…",
"CheckoutPage.paymentExpiredMessage": "Payment was not completed in 15 minutes. Please go back to {listingLink} and try again.",
"CheckoutPage.paymentInfo": "You'll only be charged if your request is accepted by the provider.",
"CheckoutPage.perDay": "per day",
Expand Down Expand Up @@ -477,7 +478,7 @@
"PaymentMethodsForm.billingDetailsNamePlaceholder": "Enter your name…",
"PaymentMethodsForm.paymentCardDetails": "Payment card details",
"PaymentMethodsForm.genericError": "Could not handle payment data. Please try again.",
"PaymentMethodsForm.handleCardPaymentError": "We are unable to authenticate your payment method. Please check your authentication details and try again.",
"PaymentMethodsForm.confirmCardPaymentError": "We are unable to authenticate your payment method. Please check your authentication details and try again.",
"PaymentMethodsForm.infoText": "I authorise Saunatime to send instructions to the financial institution that issued my card to take payments from my card account in accordance with the terms of my agreement with you.",
"PaymentMethodsForm.paymentHeading": "Payment",
"PaymentMethodsPage.heading": "Payment methods",
Expand Down Expand Up @@ -843,7 +844,7 @@
"StripePaymentForm.billingDetailsNamePlaceholder": "Enter your name…",
"StripePaymentForm.confirmPaymentError": "Payment has been made but we were unable to confirm the booking. Please try to confirm the booking request again! If the booking is not confirmed in time, the payment will be fully refunded.",
"StripePaymentForm.genericError": "Could not handle payment data. Please try again.",
"StripePaymentForm.handleCardPaymentError": "We are unable to authenticate your payment method. Please check your authentication details and try again.",
"StripePaymentForm.confirmCardPaymentError": "We are unable to authenticate your payment method. Please check your authentication details and try again.",
"StripePaymentForm.messageHeading": "Message",
"StripePaymentForm.messageLabel": "Say hello to your host {messageOptionalText}",
"StripePaymentForm.messageOptionalText": "• optional",
Expand Down
Loading

0 comments on commit 44e6cfe

Please sign in to comment.