Skip to content

Commit 34d037d

Browse files
committed
feat(payment): STRIPE-52 Auto-Select Address and mount shipping component for Stripe Link
1 parent 319f861 commit 34d037d

17 files changed

+957
-28
lines changed

packages/core/src/app/billing/Billing.spec.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,16 @@ describe('Billing Component', () => {
188188
expect(defaultProps.onUnhandledError)
189189
.toHaveBeenCalledWith(error);
190190
});
191+
192+
it('calls onUnhandledError if onReady was failed', async () => {
193+
defaultProps.onReady = jest.fn(() => {
194+
throw new Error();
195+
});
196+
197+
component = mount(<ComponentTest { ...defaultProps } />);
198+
199+
await new Promise(resolve => process.nextTick(resolve));
200+
201+
expect(defaultProps.onUnhandledError).toHaveBeenCalledWith(expect.any(Error));
202+
});
191203
});

packages/core/src/app/checkout/Checkout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ class Checkout extends Component<CheckoutProps & WithCheckoutProps & WithLanguag
376376
onSignIn={ this.handleShippingSignIn }
377377
onToggleMultiShipping={ this.handleToggleMultiShipping }
378378
onUnhandledError={ this.handleUnhandledError }
379+
step={ step }
379380
/>
380381
</LazyContainer>
381382
</CheckoutStep>

packages/core/src/app/shipping/RemoteShippingAddress.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ describe('RemoteShippingAddress Component', () => {
1818
initialize: jest.fn(),
1919
deinitialize: jest.fn(),
2020
onFieldChange: jest.fn(),
21+
onUnhandledError: jest.fn(),
2122
};
2223

2324
const initialFormikValues = {
@@ -79,4 +80,22 @@ describe('RemoteShippingAddress Component', () => {
7980

8081
expect(defaultProps.onFieldChange).toHaveBeenCalledWith(inputFieldName, 'foo');
8182
});
83+
84+
it('calls onUnhandledError if initialize was failed', () => {
85+
defaultProps.initialize = jest.fn(() => { throw new Error(); });
86+
87+
shallow(<RemoteShippingAddress { ...defaultProps } />);
88+
89+
expect(defaultProps.onUnhandledError).toHaveBeenCalledWith(expect.any(Error));
90+
});
91+
92+
it('calls onUnhandledError if deinitialize was failed', async () => {
93+
defaultProps.deinitialize = jest.fn(() => {
94+
throw new Error();
95+
});
96+
97+
shallow(<RemoteShippingAddress { ...defaultProps } />).unmount();
98+
99+
expect(defaultProps.onUnhandledError).toHaveBeenCalledWith(expect.any(Error));
100+
});
82101
});

packages/core/src/app/shipping/Shipping.spec.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getCart } from '../cart/carts.mock';
77
import { getPhysicalItem } from '../cart/lineItem.mock';
88
import { CheckoutProvider } from '../checkout';
99
import { getCheckout } from '../checkout/checkouts.mock';
10+
import CheckoutStepType from '../checkout/CheckoutStepType';
1011
import { getStoreConfig } from '../config/config.mock';
1112
import { getCustomer } from '../customer/customers.mock';
1213
import { createLocaleContext, LocaleContext, LocaleContextType } from '../locale';
@@ -36,6 +37,13 @@ describe('Shipping Component', () => {
3637
onToggleMultiShipping: jest.fn(),
3738
cartHasChanged: false,
3839
onSignIn: jest.fn(),
40+
step: { isActive: true,
41+
isComplete: true,
42+
isEditable: true,
43+
isRequired: true,
44+
type: CheckoutStepType.Shipping },
45+
isStripeLinkEnabled: true,
46+
isStripeLoading: false,
3947
navigateNextStep: jest.fn(),
4048
onUnhandledError: jest.fn(),
4149
};
@@ -104,6 +112,18 @@ describe('Shipping Component', () => {
104112
.toHaveBeenCalled();
105113
});
106114

115+
it('loads shipping data when component is mounted and stripeupe is enable', () => {
116+
jest.spyOn(checkoutState.data, 'getCustomer')
117+
.mockReturnValue({ ...getCustomer(), email: '' ,addresses: [] });
118+
mount(<ComponentTest { ...defaultProps }/>);
119+
120+
expect(checkoutService.loadShippingAddressFields)
121+
.toHaveBeenCalled();
122+
123+
expect(checkoutService.loadShippingOptions)
124+
.toHaveBeenCalled();
125+
});
126+
107127
it('triggers callback when shipping data is loaded', async () => {
108128
const handleReady = jest.fn();
109129

packages/core/src/app/shipping/Shipping.tsx

Lines changed: 114 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { createSelector } from 'reselect';
55

66
import { isEqualAddress, mapAddressFromFormValues } from '../address';
77
import { withCheckout, CheckoutContextProps } from '../checkout';
8+
import CheckoutStepStatus from '../checkout/CheckoutStepStatus';
89
import { EMPTY_ARRAY } from '../common/utility';
10+
import { PaymentMethodId } from '../payment/paymentMethod';
911
import { LoadingOverlay } from '../ui/loading';
1012

1113
import { UnassignItemError } from './errors';
@@ -20,6 +22,7 @@ export interface ShippingProps {
2022
isBillingSameAsShipping: boolean;
2123
cartHasChanged: boolean;
2224
isMultiShippingMode: boolean;
25+
step: CheckoutStepStatus;
2326
onCreateAccount(): void;
2427
onToggleMultiShipping(): void;
2528
onReady?(): void;
@@ -40,12 +43,15 @@ export interface WithCheckoutShippingProps {
4043
isGuest: boolean;
4144
isInitializing: boolean;
4245
isLoading: boolean;
46+
isStripeLoading: boolean;
47+
isStripeAutoStep: boolean;
4348
isShippingStepPending: boolean;
4449
methodId?: string;
4550
shippingAddress?: Address;
4651
shouldShowAddAddressInCheckout: boolean;
4752
shouldShowMultiShipping: boolean;
4853
shouldShowOrderComments: boolean;
54+
isStripeLinkEnabled?: boolean;
4955
assignItem(consignment: ConsignmentAssignmentRequestBody): Promise<CheckoutSelectors>;
5056
deinitializeShippingMethod(options: ShippingRequestOptions): Promise<CheckoutSelectors>;
5157
deleteConsignments(): Promise<Address | undefined>;
@@ -59,10 +65,13 @@ export interface WithCheckoutShippingProps {
5965
updateBillingAddress(address: Partial<Address>): Promise<CheckoutSelectors>;
6066
updateCheckout(payload: CheckoutRequestBody): Promise<CheckoutSelectors>;
6167
updateShippingAddress(address: Partial<Address>): Promise<CheckoutSelectors>;
68+
loadPaymentMethods(): Promise<CheckoutSelectors>;
6269
}
6370

6471
interface ShippingState {
6572
isInitializing: boolean;
73+
isStripeLoading: boolean;
74+
isStripeAutoStep: boolean;
6675
}
6776

6877
class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, ShippingState> {
@@ -71,13 +80,17 @@ class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, Ship
7180

7281
this.state = {
7382
isInitializing: true,
83+
isStripeLoading: true,
84+
isStripeAutoStep: false,
85+
7486
};
7587
}
7688

7789
async componentDidMount(): Promise<void> {
7890
const {
7991
loadShippingAddressFields,
8092
loadShippingOptions,
93+
loadPaymentMethods,
8194
onReady = noop,
8295
onUnhandledError = noop,
8396
} = this.props;
@@ -89,6 +102,7 @@ class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, Ship
89102
]);
90103

91104
onReady();
105+
await loadPaymentMethods();
92106
} catch (error) {
93107
onUnhandledError(error);
94108
} finally {
@@ -108,43 +122,106 @@ class Shipping extends Component<ShippingProps & WithCheckoutShippingProps, Ship
108122
deinitializeShippingMethod,
109123
isMultiShippingMode,
110124
onToggleMultiShipping,
125+
isStripeLinkEnabled,
126+
step,
111127
...shippingFormProps
112128
} = this.props;
113129

114130
const {
115131
isInitializing,
132+
isStripeLoading,
133+
isStripeAutoStep,
116134
} = this.state;
117135

118-
return (
119-
<div className="checkout-form">
120-
<ShippingHeader
121-
isGuest={ isGuest }
122-
isMultiShippingMode={ isMultiShippingMode }
123-
onMultiShippingChange={ this.handleMultiShippingModeSwitch }
124-
shouldShowMultiShipping={ shouldShowMultiShipping }
125-
/>
126-
127-
<LoadingOverlay
128-
isLoading={ isInitializing }
129-
unmountContentWhenLoading
130-
>
131-
<ShippingForm
132-
{ ...shippingFormProps }
133-
addresses={ customer.addresses }
134-
deinitialize={ deinitializeShippingMethod }
135-
initialize={ initializeShippingMethod }
136-
isBillingSameAsShipping = { isBillingSameAsShipping }
136+
const renderShipping = () => {
137+
if (isStripeLinkEnabled && !customer.email) {
138+
return <div className="checkout-form">
139+
<div style={ {display: isStripeAutoStep ? 'none' : undefined,} }>
140+
<LoadingOverlay
141+
hideContentWhenLoading
142+
isLoading={ isStripeLoading }
143+
>
144+
<ShippingHeader
145+
isGuest={ isGuest }
146+
isMultiShippingMode={ isMultiShippingMode }
147+
onMultiShippingChange={ this.handleMultiShippingModeSwitch }
148+
shouldShowMultiShipping={ shouldShowMultiShipping }
149+
/>
150+
151+
<LoadingOverlay
152+
isLoading={ isInitializing }
153+
unmountContentWhenLoading
154+
>
155+
<ShippingForm
156+
{ ...shippingFormProps }
157+
addresses={ customer.addresses }
158+
customerEmail={ customer.email }
159+
deinitialize={ deinitializeShippingMethod }
160+
initialize={ initializeShippingMethod }
161+
isBillingSameAsShipping = { isBillingSameAsShipping }
162+
isGuest={ isGuest }
163+
isMultiShippingMode={ isMultiShippingMode }
164+
isStripeAutoStep={ this.handleIsAutoStep }
165+
isStripeLinkEnabled={ isStripeLinkEnabled }
166+
isStripeLoading={ this.handleIsStripeLoading }
167+
onMultiShippingSubmit={ this.handleMultiShippingSubmit }
168+
onSingleShippingSubmit={ this.handleSingleShippingSubmit }
169+
onUseNewAddress={ this.handleUseNewAddress }
170+
shouldShowSaveAddress={ !isGuest }
171+
step={ step }
172+
updateAddress={ updateShippingAddress }
173+
/>
174+
</LoadingOverlay>
175+
</LoadingOverlay>
176+
</div>
177+
</div>
178+
}
179+
180+
return <div className="checkout-form">
181+
<ShippingHeader
137182
isGuest={ isGuest }
138183
isMultiShippingMode={ isMultiShippingMode }
139-
onMultiShippingSubmit={ this.handleMultiShippingSubmit }
140-
onSingleShippingSubmit={ this.handleSingleShippingSubmit }
141-
onUseNewAddress={ this.handleUseNewAddress }
142-
shouldShowSaveAddress={ !isGuest }
143-
updateAddress={ updateShippingAddress }
184+
onMultiShippingChange={ this.handleMultiShippingModeSwitch }
185+
shouldShowMultiShipping={ shouldShowMultiShipping }
144186
/>
145-
</LoadingOverlay>
187+
188+
<LoadingOverlay
189+
isLoading={ isInitializing }
190+
unmountContentWhenLoading
191+
>
192+
<ShippingForm
193+
{ ...shippingFormProps }
194+
addresses={ customer.addresses }
195+
customerEmail={ customer.email }
196+
deinitialize={ deinitializeShippingMethod }
197+
initialize={ initializeShippingMethod }
198+
isBillingSameAsShipping = { isBillingSameAsShipping }
199+
isGuest={ isGuest }
200+
isMultiShippingMode={ isMultiShippingMode }
201+
isStripeAutoStep={ this.handleIsAutoStep }
202+
isStripeLinkEnabled={ isStripeLinkEnabled }
203+
isStripeLoading={ this.handleIsStripeLoading }
204+
onMultiShippingSubmit={ this.handleMultiShippingSubmit }
205+
onSingleShippingSubmit={ this.handleSingleShippingSubmit }
206+
onUseNewAddress={ this.handleUseNewAddress }
207+
shouldShowSaveAddress={ !isGuest }
208+
step={ step }
209+
updateAddress={ updateShippingAddress }
210+
/>
211+
</LoadingOverlay>
146212
</div>
147-
);
213+
214+
}
215+
216+
return ( renderShipping() );
217+
}
218+
219+
private handleIsStripeLoading: () => void = () => {
220+
this.setState({ isStripeLoading: false });
221+
}
222+
223+
private handleIsAutoStep: () => void = () => {
224+
this.setState({ isStripeAutoStep: true });
148225
}
149226

150227
private handleMultiShippingModeSwitch: () => void = async () => {
@@ -284,6 +361,7 @@ const deleteConsignmentsSelector = createSelector(
284361
}
285362
);
286363

364+
// tslint:disable-next-line:cyclomatic-complexity
287365
export function mapToShippingProps({
288366
checkoutService,
289367
checkoutState,
@@ -299,6 +377,7 @@ export function mapToShippingProps({
299377
getBillingAddress,
300378
getShippingAddressFields,
301379
getShippingCountries,
380+
getPaymentMethod,
302381
},
303382
statuses: {
304383
isShippingStepPending,
@@ -350,6 +429,11 @@ export function mapToShippingProps({
350429
shippableItemsCount < 50
351430
);
352431
const countriesWithAutocomplete = ['US', 'CA', 'AU', 'NZ'];
432+
const stripeUpe = getPaymentMethod('card', PaymentMethodId.StripeUPE);
433+
const linkEnabled = stripeUpe?.initializationData.enableLink || false;
434+
const stripeUpeSupportedCurrency = cart?.currency.code === 'USD' || false;
435+
const stripeUpeLinkEnabled = linkEnabled && stripeUpeSupportedCurrency;
436+
353437

354438
if (features['CHECKOUT-4183.checkout_google_address_autocomplete_uk']) {
355439
countriesWithAutocomplete.push('GB');
@@ -375,6 +459,8 @@ export function mapToShippingProps({
375459
isGuest: customer.isGuest,
376460
isInitializing: isLoadingShippingCountries() || isLoadingShippingOptions(),
377461
isLoading,
462+
isStripeLoading: false,
463+
isStripeAutoStep: false,
378464
isShippingStepPending: isShippingStepPending(),
379465
loadShippingAddressFields: checkoutService.loadShippingAddressFields,
380466
loadShippingOptions: checkoutService.loadShippingOptions,
@@ -388,6 +474,8 @@ export function mapToShippingProps({
388474
updateBillingAddress: checkoutService.updateBillingAddress,
389475
updateCheckout: checkoutService.updateCheckout,
390476
updateShippingAddress: checkoutService.updateShippingAddress,
477+
isStripeLinkEnabled: stripeUpeLinkEnabled,
478+
loadPaymentMethods: checkoutService.loadPaymentMethods,
391479
};
392480
}
393481

0 commit comments

Comments
 (0)