diff --git a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/index.tsx b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/index.tsx index 75e270bf6f87..9b44725cb694 100644 --- a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/index.tsx +++ b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/index.tsx @@ -19,6 +19,7 @@ import getSite from 'calypso/state/sites/selectors/get-site'; import { setSelectedSiteId } from 'calypso/state/ui/actions'; import ClientCheckoutError from './checkout-error'; import ClientCheckoutPlaceholder from './checkout-placeholder'; +import getPurchasedWPCOMPlanSlug from './lib/get-purchased-wpcom-plan-slug'; import getSuccessRedirectUrl from './lib/get-success-redirect-url'; import type { ShoppingCartItem } from '../types'; @@ -253,7 +254,8 @@ function BillingDragonCheckoutContent( { sitelessCheckoutType="a4a" redirectTo={ getSuccessRedirectUrl( window.location.origin, - shouldClearCartOnSuccess && ! isPlanCheckout + shouldClearCartOnSuccess && ! isPlanCheckout, + isPlanCheckout ? null : getPurchasedWPCOMPlanSlug( cartItems ) ) } customizedPreviousPath="/marketplace" siteSlug={ siteSlug ?? '' } diff --git a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-purchased-wpcom-plan-slug.ts b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-purchased-wpcom-plan-slug.ts new file mode 100644 index 000000000000..e630ac7342c8 --- /dev/null +++ b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-purchased-wpcom-plan-slug.ts @@ -0,0 +1,11 @@ +import { isWPCOMHostingProduct } from '../../lib/hosting'; +import type { ShoppingCartItem } from '../../types'; + +/** + * A cart holding a WordPress.com plan lands on the "Needs setup" page rather + * than the licenses list, so the agency can provision the site right away. The + * returned slug is what that page's confirmation banner reports to Tracks. + */ +export default function getPurchasedWPCOMPlanSlug( cartItems: ShoppingCartItem[] ): string | null { + return cartItems.find( ( { slug } ) => isWPCOMHostingProduct( slug ) )?.slug ?? null; +} diff --git a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-success-redirect-url.ts b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-success-redirect-url.ts index 9718c7584fbc..1a716c4d5585 100644 --- a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-success-redirect-url.ts +++ b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/get-success-redirect-url.ts @@ -1,16 +1,31 @@ +import { + A4A_LICENSES_LINK, + A4A_SITES_LINK_NEEDS_SETUP, +} from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; + /** * The checkout pending page replaces `:receiptId` with the real receipt ID, but * only when the transaction succeeds — failed or cancelled payments are sent - * back to checkout instead. This makes `receipt_id` on the licenses page a + * back to checkout instead. This makes `receipt_id` on the landing page a * reliable "successful purchase" signal used to clear the persisted mini-cart. */ export const RECEIPT_ID_PLACEHOLDER = ':receiptId'; export default function getSuccessRedirectUrl( origin: string, - shouldClearCartOnSuccess: boolean + shouldClearCartOnSuccess: boolean, + wpcomPlanSlug?: string | null ): string { - const url = `${ origin }/purchases/licenses`; + const url = `${ origin }${ wpcomPlanSlug ? A4A_SITES_LINK_NEEDS_SETUP : A4A_LICENSES_LINK }`; + + const queryArgs = [ + ...( wpcomPlanSlug + ? [ `wpcom_creator_purchased=${ encodeURIComponent( wpcomPlanSlug ) }` ] + : [] ), + // Built by hand rather than with `addQueryArgs`, which would percent-encode + // the colon the pending page looks for when interpolating the receipt ID. + ...( shouldClearCartOnSuccess ? [ `receipt_id=${ RECEIPT_ID_PLACEHOLDER }` ] : [] ), + ]; - return shouldClearCartOnSuccess ? `${ url }?receipt_id=${ RECEIPT_ID_PLACEHOLDER }` : url; + return queryArgs.length ? `${ url }?${ queryArgs.join( '&' ) }` : url; } diff --git a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-purchased-wpcom-plan-slug.test.ts b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-purchased-wpcom-plan-slug.test.ts new file mode 100644 index 000000000000..c12ab8bd5693 --- /dev/null +++ b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-purchased-wpcom-plan-slug.test.ts @@ -0,0 +1,31 @@ +import getPurchasedWPCOMPlanSlug from '../get-purchased-wpcom-plan-slug'; +import type { ShoppingCartItem } from '../../../types'; + +const cartItem = ( slug: string ) => ( { slug } ) as ShoppingCartItem; + +describe( 'getPurchasedWPCOMPlanSlug', () => { + it( 'returns the slug of the purchased WordPress.com plan', () => { + expect( getPurchasedWPCOMPlanSlug( [ cartItem( 'wpcom-hosting-business' ) ] ) ).toBe( + 'wpcom-hosting-business' + ); + } ); + + it( 'matches a WordPress.com plan alongside other products, as the legacy flow did', () => { + expect( + getPurchasedWPCOMPlanSlug( [ + cartItem( 'jetpack-backup' ), + cartItem( 'wpcom-hosting-business' ), + ] ) + ).toBe( 'wpcom-hosting-business' ); + } ); + + it( 'returns null when no WordPress.com plan is in the cart', () => { + expect( + getPurchasedWPCOMPlanSlug( [ cartItem( 'jetpack-backup' ), cartItem( 'pressable-build' ) ] ) + ).toBeNull(); + } ); + + it( 'returns null for an empty cart', () => { + expect( getPurchasedWPCOMPlanSlug( [] ) ).toBeNull(); + } ); +} ); diff --git a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-success-redirect-url.test.ts b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-success-redirect-url.test.ts index 38b010c26ff0..93d5451123ad 100644 --- a/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-success-redirect-url.test.ts +++ b/client/a8c-for-agencies/sections/marketplace/billing-dragon-checkout/lib/test/get-success-redirect-url.test.ts @@ -20,4 +20,26 @@ describe( 'getSuccessRedirectUrl', () => { `receipt_id=${ RECEIPT_ID_PLACEHOLDER }` ); } ); + + it( 'sends WordPress.com purchases to the needs setup page with the purchased plan', () => { + expect( + getSuccessRedirectUrl( 'https://agencies.automattic.com', false, 'wpcom-hosting-business' ) + ).toBe( + 'https://agencies.automattic.com/sites/need-setup?wpcom_creator_purchased=wpcom-hosting-business' + ); + } ); + + it( 'keeps the receipt ID placeholder alongside the purchased plan', () => { + expect( + getSuccessRedirectUrl( 'https://agencies.automattic.com', true, 'wpcom-hosting-business' ) + ).toBe( + 'https://agencies.automattic.com/sites/need-setup?wpcom_creator_purchased=wpcom-hosting-business&receipt_id=:receiptId' + ); + } ); + + it( 'falls back to the licenses URL when no WordPress.com plan was purchased', () => { + expect( getSuccessRedirectUrl( 'https://agencies.automattic.com', false, null ) ).toBe( + 'https://agencies.automattic.com/purchases/licenses' + ); + } ); } ); diff --git a/client/a8c-for-agencies/sections/sites/needs-setup-sites/index.tsx b/client/a8c-for-agencies/sections/sites/needs-setup-sites/index.tsx index 2b41bc0fdc30..77a7e899a5bf 100644 --- a/client/a8c-for-agencies/sections/sites/needs-setup-sites/index.tsx +++ b/client/a8c-for-agencies/sections/sites/needs-setup-sites/index.tsx @@ -13,6 +13,7 @@ import useCreateWPCOMSiteMutation from 'calypso/a8c-for-agencies/data/sites/use- import useFetchPendingSites from 'calypso/a8c-for-agencies/data/sites/use-fetch-pending-sites'; import useSiteCreatedCallback from 'calypso/a8c-for-agencies/hooks/use-site-created-callback'; import useTrackProvisioningSites from 'calypso/a8c-for-agencies/hooks/use-track-provisioning-sites'; +import useClearCartOnCheckoutSuccess from 'calypso/a8c-for-agencies/sections/marketplace/hooks/use-clear-cart-on-checkout-success'; import LayoutColumn from 'calypso/layout/hosting-dashboard/column'; import LayoutHeader, { LayoutHeaderTitle as Title, @@ -47,6 +48,8 @@ export default function NeedSetup( { licenseKey }: Props ) { null ); + useClearCartOnCheckoutSuccess(); + const closeModal = () => setCurrentSiteConfigurationId( null ); const title = translate( 'Sites' );