Skip to content

Commit

Permalink
Payments: scaffold the checkout pending page, the 2nd attempt. (#23827)
Browse files Browse the repository at this point in the history
* Implement a placeholder version of `<CheckoutPending>` component.

* Register pending routes, `/checkout/thank-you/<site>/pending/:orderId`
to the controller.

* Update the route to include parsing `site` path parameter

* Direct to different ending routes accordingly.

* update `getSourcePaymentTransactionDetail()` as `getOrderTransaction()`

* Dispatch `setOrderTransactionError` on error.

* Granularize the routing rules of the pending checkout page.

* `showErrorNotice()` need to be called after `page()`. Otherwise, the
notice would be eliminated before showing.

* Fix the case handling for the general API error.

* Update the copy according to the copy review feedbacks.

* Redirect users back to the plan page instead of the homepage in case of
payment cancellation or technical errors.
  • Loading branch information
southp committed Apr 13, 2018
1 parent c608ae5 commit caedb8a
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 18 deletions.
113 changes: 113 additions & 0 deletions client/my-sites/checkout/checkout-thank-you/pending.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/** @format */

/**
* External dependencies
*/
import page from 'page';
import { localize } from 'i18n-calypso';
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { identity } from 'lodash';

/**
* Internal dependencies
*/
import { getOrderTransaction, getOrderTransactionError } from 'state/selectors';
import { ORDER_TRANSACTION_STATUS } from 'state/order-transactions/constants';
import { errorNotice } from 'state/notices/actions';

class CheckoutPending extends PureComponent {
static propTypes = {
orderId: PropTypes.number.isRequired,
siteSlug: PropTypes.string.isRequired,
transaction: PropTypes.object,
error: PropTypes.object,
errorNotice: PropTypes.func,
localize: PropTypes.func,
};

static defaultProps = {
localize: identity,
errorNotice: identity,
};

componentWillReceiveProps( nextProps ) {
const { transaction, error } = nextProps;
const { translate, showErrorNotice, siteSlug } = this.props;

const retryOnError = () => {
page( `/checkout/${ siteSlug }` );

showErrorNotice(
translate( "Sorry, we couldn't process your payment. Please try again later." )
);
};

const planRoute = `/plans/my-plan/${ siteSlug }`;

if ( transaction ) {
const { processingStatus } = transaction;

if ( ORDER_TRANSACTION_STATUS.SUCCESS === processingStatus ) {
page( `/checkout/thank-you/${ siteSlug }` );

return;
}

// It is mostly because the user has cancelled the payment.
// See the explanation in https://github.com/Automattic/wp-calypso/pull/23670#issuecomment-377186515
if ( ORDER_TRANSACTION_STATUS.FAILURE === processingStatus ) {
// Bring the user back to the plan page in this case.
page( planRoute );

return;
}

// or the processing status indicates that there was something wrong.
if ( ORDER_TRANSACTION_STATUS.ERROR === processingStatus ) {
// redirect users back to the checkout page so they can try again.
retryOnError();

return;
}

// The API has responded a status string that we don't expect somehow.
if ( ORDER_TRANSACTION_STATUS.UNKNOWN === processingStatus ) {
// Redirect users back to the plan page so that they won't be stuck here.
page( planRoute );

showErrorNotice( translate( 'Oops! Something went wrong. Please try again later.' ) );

return;
}
}

// A HTTP error occurs. We use the same handling
if ( error ) {
retryOnError();
}
}

render() {
const { orderId } = this.props;

// TODO:
// Replace this placeholder by the real one
return (
<div>
<p>Waiting for the payment result of { orderId }</p>
</div>
);
}
}

export default connect(
( state, props ) => ( {
transaction: getOrderTransaction( state, props.orderId ),
error: getOrderTransactionError( state, props.orderId ),
} ),
{
showErrorNotice: errorNotice,
}
)( localize( CheckoutPending ) );
19 changes: 19 additions & 0 deletions client/my-sites/checkout/controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Checkout from './checkout';
import CheckoutData from 'components/data/checkout';
import CartData from 'components/data/cart';
import SecondaryCart from './cart/secondary-cart';
import CheckoutPendingComponent from './checkout-thank-you/pending';
import CheckoutThankYouComponent from './checkout-thank-you';

const checkoutRoutes = [
Expand All @@ -36,6 +37,11 @@ const checkoutGSuiteNudgeRoutes = [
new Route( '/checkout/:site/with-gsuite/:domain' ),
];

const checkoutPendingRoutes = [
new Route( '/checkout/thank-you/no-site/pending/:orderId' ),
new Route( '/checkout/thank-you/:site/pending/:orderId' ),
];

const checkoutThankYouRoutes = [
new Route( '/checkout/thank-you/no-site/:receipt' ),
new Route( '/checkout/thank-you/no-site' ),
Expand Down Expand Up @@ -104,6 +110,19 @@ export default {
next();
},

checkoutPending: function( context, next ) {
const { routePath, routeParams } = sectionifyWithRoutes( context.path, checkoutPendingRoutes );
const orderId = Number( context.params.orderId );
const siteSlug = context.params.site;

analytics.pageView.record( routePath, 'Checkout Pending', routeParams );
context.store.dispatch( setSection( { name: 'checkout-thank-you' }, { hasSidebar: false } ) );

context.primary = <CheckoutPendingComponent orderId={ orderId } siteSlug={ siteSlug } />;

next();
},

checkoutThankYou: function( context, next ) {
const { routePath, routeParams } = sectionifyWithRoutes( context.path, checkoutThankYouRoutes );
const receiptId = Number( context.params.receiptId );
Expand Down
16 changes: 16 additions & 0 deletions client/my-sites/checkout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ export default function() {
return;
}

page(
'/checkout/thank-you/no-site/pending/:orderId',
siteSelection,
checkoutController.checkoutPending,
makeLayout,
clientRender
);

page(
'/checkout/thank-you/no-site/:receiptId?',
noSite,
Expand All @@ -40,6 +48,14 @@ export default function() {
clientRender
);

page(
'/checkout/thank-you/:site/pending/:orderId',
siteSelection,
checkoutController.checkoutPending,
makeLayout,
clientRender
);

page(
'/checkout/thank-you/:site/:receiptId?',
siteSelection,
Expand Down
9 changes: 2 additions & 7 deletions client/state/data-layer/wpcom/me/transactions/order/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
/**
* External dependencies
*/
import { translate } from 'i18n-calypso';

/**
* Internal dependencies
*/
import { http } from 'state/data-layer/wpcom-http/actions';
import { dispatchRequestEx } from 'state/data-layer/wpcom-http/utils';
import { errorNotice } from 'state/notices/actions';
import { setOrderTransaction } from 'state/order-transactions/actions';
import { setOrderTransaction, setOrderTransactionError } from 'state/order-transactions/actions';
import { ORDER_TRANSACTION_FETCH } from 'state/action-types';
import fromApi from './from-api';

Expand All @@ -32,10 +30,7 @@ export const fetchOrderTransaction = action =>

export const onSuccess = ( { orderId }, detail ) => setOrderTransaction( orderId, detail );

export const onError = () =>
errorNotice(
translate( 'We have problems fetching your payment status. Please try again later.' )
);
export const onError = ( { orderId }, error ) => setOrderTransactionError( orderId, error );

export default {
[ ORDER_TRANSACTION_FETCH ]: [
Expand Down
21 changes: 10 additions & 11 deletions client/state/data-layer/wpcom/me/transactions/order/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@
*/
import { fetchOrderTransaction, onSuccess, onError } from '../';
import { http } from 'state/data-layer/wpcom-http/actions';
import { errorNotice } from 'state/notices/actions';
import { setOrderTransaction } from 'state/order-transactions/actions';

// we are mocking impure-lodash here, so that conciergeShiftsFetchError() will contain the expected id in the tests
jest.mock( 'lib/impure-lodash', () => ( {
uniqueId: () => 'mock-unique-id',
} ) );
import { setOrderTransaction, setOrderTransactionError } from 'state/order-transactions/actions';

describe( 'wpcom-api', () => {
describe( 'me/transactions/order', () => {
const orderId = 123;

describe( 'fetchOrderTransaction()', () => {
test( 'should return the expected http request action.', () => {
const action = {
orderId: 123,
orderId,
};

expect( fetchOrderTransaction( action ) ).toEqual(
Expand All @@ -40,7 +36,7 @@ describe( 'wpcom-api', () => {
describe( 'onSuccess()', () => {
test( 'should return the expected setting action for populating state.', () => {
const action = {
orderId: 123,
orderId,
};
const detail = {
status: 'profit!',
Expand All @@ -54,8 +50,11 @@ describe( 'wpcom-api', () => {

describe( 'onError()', () => {
test( 'should return the expected error notice action.', () => {
expect( onError() ).toEqual(
errorNotice( 'We have problems fetching your payment status. Please try again later.' )
const error = {
message: 'something goes wrong!',
};
expect( onError( { orderId }, error ) ).toEqual(
setOrderTransactionError( orderId, error )
);
} );
} );
Expand Down

0 comments on commit caedb8a

Please sign in to comment.