Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkout: Decouple existingCardProcessor from checkout #48069

Merged
merged 34 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b0c1e2f
WIP: Move existingCardProcessor to own file
sirbrillig Dec 5, 2020
d3b1051
Include transactionOptions with dataForProcessor
sirbrillig Dec 5, 2020
3a8dc61
Move transaction translate functions to translate-cart file
sirbrillig Dec 5, 2020
927bb71
Add CardProcessorOptions type
sirbrillig Dec 5, 2020
69293b9
Add stripeConfiguration to dataForProcessor
sirbrillig Dec 5, 2020
cf3ec74
Make TransactionRequestWithLineItems more permissive for falsy values
sirbrillig Dec 5, 2020
23febdf
Remove stripeConfiguration from ExistingCard payment method
sirbrillig Dec 5, 2020
5a863f8
Finish existingCardProcessor
sirbrillig Dec 5, 2020
866e5ff
Allow stripeConfiguration to be null in CardProcessorOptions
sirbrillig Dec 5, 2020
2cdb22e
Apply ExistingCardProcessorData type to processor function call
sirbrillig Dec 5, 2020
b9d99e2
Guard against missing stripeConfiguration in existingCardProcessor
sirbrillig Dec 5, 2020
8b9e30a
Move getDomainDetails to own file
sirbrillig Dec 5, 2020
d411d6e
Allow siteId to be a number in TransactionRequestWithLineItems
sirbrillig Dec 6, 2020
84038c5
Remove createExistingCardMethod from composite-checkout README
sirbrillig Dec 6, 2020
a68d39b
Move getPostalCode to own file
sirbrillig Dec 6, 2020
addfef7
Stop passing total to existing-card payment processor
sirbrillig Dec 6, 2020
e276d0a
Add mergeIfObjects helper
sirbrillig Dec 6, 2020
13b6ab8
Call existingCardProcessor with additional data already included
sirbrillig Dec 6, 2020
0bff489
Make existingCardProcessor require all data as arguments
sirbrillig Dec 6, 2020
d8dc765
Make sure siteId is always a string in the existingCardProcessor
sirbrillig Dec 6, 2020
cf2cd95
Change getDomainDetails to return undefined for missing data
sirbrillig Dec 6, 2020
1895456
Make siteId a string or undefined in existingCardProcessor
sirbrillig Dec 6, 2020
4e4b1d3
Disallow country being undefined in TransactionRequestWithLineItems
sirbrillig Dec 6, 2020
2bdbf31
Ensure all required fields are checked in isValidTransactionData
sirbrillig Dec 7, 2020
87a6a6b
Change mergeIfObjects to support an array of objects
sirbrillig Dec 7, 2020
bd945fe
Change usage of mergeIfObjects to array
sirbrillig Dec 7, 2020
180278f
Remove paymentMethodType from transactionData type as it is added later
sirbrillig Dec 9, 2020
89f4ff4
Remove saveTransactionResponseToWpcomStore from existingCardProcessor
sirbrillig Dec 10, 2020
3e6942b
Change mergeIfObjects to accept variadic args rather than an array
sirbrillig Dec 10, 2020
8f4aec1
Change isValidTransactionData to throw specific errors
sirbrillig Dec 10, 2020
3a54360
Rename CardProcessorOptions to PaymentProcessorOptions
sirbrillig Dec 14, 2020
83b30ca
Organize TransactionRequestWithLineItems properties
sirbrillig Dec 15, 2020
5b45c33
Clarify requried properties in ExistingCardTransactionRequest
sirbrillig Dec 15, 2020
6040262
Pass stripe/configuration directly to assignNewCardProcessor
sirbrillig Dec 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function ChangePaymentMethodList( {
const currentlyAssignedPaymentMethodId = 'existingCard-' + currentPaymentMethod.stored_details_id; // TODO: make this work for paypal.

const translate = useTranslate();
const { isStripeLoading } = useStripe();
const { isStripeLoading, stripe, stripeConfiguration } = useStripe();
const paymentMethods = useAssignablePaymentMethods();

const showErrorMessage = useCallback( ( error ) => {
Expand Down Expand Up @@ -218,7 +218,10 @@ function ChangePaymentMethodList( {
paymentProcessors={ {
'existing-card': ( data ) => assignExistingCardProcessor( purchase, data ),
card: ( data ) =>
assignNewCardProcessor( { purchase, translate, siteSlug, apiParams }, data ),
assignNewCardProcessor(
{ purchase, translate, siteSlug, apiParams, stripe, stripeConfiguration },
data
),
} }
isLoading={ isStripeLoading }
initiallySelectedPaymentMethodId={ currentlyAssignedPaymentMethodId }
Expand Down Expand Up @@ -252,8 +255,8 @@ async function assignExistingCardProcessor( purchase, { storedDetailsId } ) {
}

async function assignNewCardProcessor(
{ purchase, translate, siteSlug, apiParams },
{ stripe, stripeConfiguration, name, countryCode, postalCode }
{ purchase, translate, siteSlug, apiParams, stripe, stripeConfiguration },
{ name, countryCode, postalCode }
) {
const createStripeSetupIntentAsync = async ( paymentDetails ) => {
const { country, 'postal-code': zip } = paymentDetails;
Expand Down
52 changes: 47 additions & 5 deletions client/my-sites/checkout/composite-checkout/composite-checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ import {
freePurchaseProcessor,
multiPartnerCardProcessor,
fullCreditsProcessor,
existingCardProcessor,
payPalProcessor,
genericRedirectProcessor,
weChatProcessor,
} from './payment-method-processors';
import existingCardProcessor from './lib/existing-card-processor';
import useGetThankYouUrl from './hooks/use-get-thank-you-url';
import createAnalyticsEventHandler from './record-analytics';
import { useProductVariants } from './hooks/product-variants';
Expand Down Expand Up @@ -84,6 +84,9 @@ import { WPCOMCartItem } from './types/checkout-cart';
import doesValueExist from './lib/does-value-exist';
import EmptyCart from './components/empty-cart';
import getContactDetailsType from './lib/get-contact-details-type';
import getDomainDetails from './lib/get-domain-details';
import getPostalCode from './lib/get-postal-code';
import mergeIfObjects from './lib/merge-if-objects';
import type { ReactStandardAction } from './types/analytics';
import useCreatePaymentCompleteCallback from './hooks/use-create-payment-complete-callback';

Expand Down Expand Up @@ -139,7 +142,6 @@ export default function CompositeCheckout( {
const isPrivate = useSelector( ( state ) => siteId && isPrivateSite( state, siteId ) ) || false;
const { stripe, stripeConfiguration, isStripeLoading, stripeLoadingError } = useStripe();
const createUserAndSiteBeforeTransaction = Boolean( isLoggedOutCart || isNoSiteCart );
const transactionOptions = { createUserAndSiteBeforeTransaction };
const reduxDispatch = useDispatch();
// eslint-disable-next-line react-hooks/exhaustive-deps
const recordEvent: ( action: ReactStandardAction ) => void = useCallback(
Expand Down Expand Up @@ -356,6 +358,7 @@ export default function CompositeCheckout( {

const contactInfo: ManagedContactDetails | undefined = select( 'wpcom' )?.getContactInfo();
const countryCode: string = contactInfo?.countryCode?.value ?? '';
const subdivisionCode: string = contactInfo?.state?.value ?? '';

const paymentMethods = arePaymentMethodsLoading
? []
Expand Down Expand Up @@ -416,13 +419,22 @@ export default function CompositeCheckout( {
const contactDetailsType = getContactDetailsType( responseCart );
const includeDomainDetails = contactDetailsType === 'domain';
const includeGSuiteDetails = contactDetailsType === 'gsuite';
const transactionOptions = { createUserAndSiteBeforeTransaction };
const dataForProcessor = useMemo(
() => ( {
includeDomainDetails,
includeGSuiteDetails,
recordEvent,
createUserAndSiteBeforeTransaction,
stripeConfiguration,
} ),
[ includeDomainDetails, includeGSuiteDetails, recordEvent ]
[
includeDomainDetails,
includeGSuiteDetails,
recordEvent,
createUserAndSiteBeforeTransaction,
stripeConfiguration,
]
);
const dataForRedirectProcessor = useMemo(
() => ( {
Expand All @@ -433,6 +445,16 @@ export default function CompositeCheckout( {
[ dataForProcessor, getThankYouUrl, siteSlug ]
);

const domainDetails = useMemo(
() =>
getDomainDetails( {
includeDomainDetails,
includeGSuiteDetails,
} ),
[ includeGSuiteDetails, includeDomainDetails ]
);
const postalCode = getPostalCode();

const paymentProcessors = useMemo(
() => ( {
'apple-pay': ( transactionData: unknown ) =>
Expand Down Expand Up @@ -466,15 +488,35 @@ export default function CompositeCheckout( {
'full-credits': ( transactionData: unknown ) =>
fullCreditsProcessor( transactionData, dataForProcessor, transactionOptions ),
'existing-card': ( transactionData: unknown ) =>
existingCardProcessor( transactionData, dataForProcessor, transactionOptions ),
existingCardProcessor(
mergeIfObjects( transactionData, {
country: countryCode,
postalCode,
subdivisionCode,
siteId: siteId ? String( siteId ) : undefined,
domainDetails,
} ),
dataForProcessor
),
paypal: ( transactionData: unknown ) =>
payPalProcessor(
transactionData,
{ ...dataForProcessor, getThankYouUrl, couponItem },
transactionOptions
),
} ),
[ couponItem, dataForProcessor, dataForRedirectProcessor, getThankYouUrl, transactionOptions ]
[
siteId,
couponItem,
dataForProcessor,
dataForRedirectProcessor,
getThankYouUrl,
transactionOptions,
countryCode,
subdivisionCode,
postalCode,
domainDetails,
]
);

const jetpackColors = isJetpackNotAtomic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* External dependencies
*/
import debugFactory from 'debug';
import { makeSuccessResponse, makeRedirectResponse } from '@automattic/composite-checkout';
import { confirmStripePaymentIntent } from '@automattic/calypso-stripe';
import type { PaymentProcessorResponse } from '@automattic/composite-checkout';

/**
* Internal dependencies
*/
import { createTransactionEndpointRequestPayloadFromLineItems } from './translate-cart';
import { wpcomTransaction } from '../payment-method-helpers';
import type { PaymentProcessorOptions } from '../types/payment-processors';
import type { ExistingCardTransactionRequestWithLineItems } from '../types/transaction-endpoint';

const debug = debugFactory( 'calypso:composite-checkout:payment-method-helpers' );

export default async function existingCardProcessor(
transactionData: unknown,
dataForProcessor: PaymentProcessorOptions
): Promise< PaymentProcessorResponse > {
if ( ! isValidTransactionData( transactionData ) ) {
throw new Error( 'Required purchase data is missing' );
}
const { stripeConfiguration, recordEvent } = dataForProcessor;
if ( ! stripeConfiguration ) {
throw new Error( 'Stripe configuration is required' );
}
return submitExistingCardPayment( transactionData, dataForProcessor )
.then( ( stripeResponse ) => {
if ( stripeResponse?.message?.payment_intent_client_secret ) {
// 3DS authentication required
recordEvent( { type: 'SHOW_MODAL_AUTHORIZATION' } );
return confirmStripePaymentIntent(
stripeConfiguration,
stripeResponse?.message?.payment_intent_client_secret
);
}
return stripeResponse;
} )
.then( ( stripeResponse ) => {
if ( stripeResponse?.redirect_url ) {
return makeRedirectResponse( stripeResponse.redirect_url );
}
return makeSuccessResponse( stripeResponse );
} );
}

async function submitExistingCardPayment(
transactionData: ExistingCardTransactionRequest,
transactionOptions: PaymentProcessorOptions
) {
debug( 'formatting existing card transaction', transactionData );
const formattedTransactionData = createTransactionEndpointRequestPayloadFromLineItems( {
...transactionData,
paymentMethodType: 'WPCOM_Billing_MoneyPress_Stored',
} );
debug( 'submitting existing card transaction', formattedTransactionData );

return wpcomTransaction( formattedTransactionData, transactionOptions );
}

type ExistingCardTransactionRequest = Omit<
ExistingCardTransactionRequestWithLineItems,
'paymentMethodType'
>;

function isValidTransactionData(
submitData: unknown
): submitData is ExistingCardTransactionRequest {
const data = submitData as ExistingCardTransactionRequest;
if ( ! ( data?.items?.length > 0 ) ) {
throw new Error( 'Transaction requires items and none were provided' );
}
// Validate data required for this payment method type. Some other data may
// be required by the server but not required here since the server will give
// a better localized error message than we can provide.
if ( ! data.siteId ) {
throw new Error( 'Transaction requires siteId and none was provided' );
}
if ( ! data.country ) {
throw new Error( 'Transaction requires country code and none was provided' );
}
if ( ! data.postalCode ) {
throw new Error( 'Transaction requires postal code and none was provided' );
}
if ( ! data.storedDetailsId ) {
throw new Error( 'Transaction requires saved card information and none was provided' );
}
if ( ! data.name ) {
throw new Error( 'Transaction requires cardholder name and none was provided' );
}
if ( ! data.paymentMethodToken ) {
throw new Error( 'Transaction requires a Stripe token and none was provided' );
}
if ( ! data.paymentPartnerProcessorId ) {
throw new Error( 'Transaction requires a processor id and none was provided' );
}
return true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { defaultRegistry } from '@automattic/composite-checkout';
import type { DomainContactDetails } from '@automattic/shopping-cart';

/**
* Internal dependencies
*/
import { prepareDomainContactDetailsForTransaction } from 'calypso/my-sites/checkout/composite-checkout/types/wpcom-store-state';
import type { ManagedContactDetails } from '../types/wpcom-store-state';

const { select } = defaultRegistry;

export default function getDomainDetails( {
includeDomainDetails,
includeGSuiteDetails,
}: {
includeDomainDetails: boolean;
includeGSuiteDetails: boolean;
} ): DomainContactDetails | undefined {
const managedContactDetails: ManagedContactDetails | undefined = select(
'wpcom'
)?.getContactInfo();
if ( ! managedContactDetails ) {
return undefined;
}
const domainDetails = prepareDomainContactDetailsForTransaction( managedContactDetails );
return includeDomainDetails || includeGSuiteDetails ? domainDetails : undefined;
}
24 changes: 24 additions & 0 deletions client/my-sites/checkout/composite-checkout/lib/get-postal-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { defaultRegistry } from '@automattic/composite-checkout';

/**
* Internal dependencies
*/
import { tryToGuessPostalCodeFormat } from 'calypso/lib/postal-code';
import type { ManagedContactDetails } from '../types/wpcom-store-state';

const { select } = defaultRegistry;

export default function getPostalCode(): string {
const managedContactDetails: ManagedContactDetails | undefined = select(
'wpcom'
)?.getContactInfo();
if ( ! managedContactDetails ) {
return '';
}
const countryCode = managedContactDetails.countryCode?.value ?? '';
const postalCode = managedContactDetails.postalCode?.value ?? '';
return tryToGuessPostalCodeFormat( postalCode.toUpperCase(), countryCode );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function mergeIfObjects( ...objects: unknown[] ): MergedObject {
return objects.reduce( ( merged: MergedObject, obj: unknown ): MergedObject => {
if ( typeof obj !== 'object' ) {
return merged;
}
return { ...merged, ...obj };
}, {} );
}

type MergedObject = Record< string, unknown >;
Loading