mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +08:00
Merge pull request #3182 from woocommerce/PCP-4232-change-onboarding-flags
Change onboarding flags (4232)
This commit is contained in:
commit
fad93e3e81
12 changed files with 613 additions and 147 deletions
|
@ -5,7 +5,7 @@
|
||||||
* @package WooCommerce\PayPalCommerce\ApiClient\Repository
|
* @package WooCommerce\PayPalCommerce\ApiClient\Repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\ApiClient\Repository;
|
namespace WooCommerce\PayPalCommerce\ApiClient\Repository;
|
||||||
|
|
||||||
|
@ -18,43 +18,21 @@ class PartnerReferralsData {
|
||||||
/**
|
/**
|
||||||
* The DCC Applies Helper object.
|
* The DCC Applies Helper object.
|
||||||
*
|
*
|
||||||
|
* @deprecated Deprecates with the new UI. In this class, the products are
|
||||||
|
* always explicit, and should not be deducted from the
|
||||||
|
* DccApplies state at this point.
|
||||||
|
* Remove this with the legacy UI code.
|
||||||
* @var DccApplies
|
* @var DccApplies
|
||||||
*/
|
*/
|
||||||
private $dcc_applies;
|
private DccApplies $dcc_applies;
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of products ('PPCP', 'EXPRESS_CHECKOUT').
|
|
||||||
*
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
private $products;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PartnerReferralsData constructor.
|
* PartnerReferralsData constructor.
|
||||||
*
|
*
|
||||||
* @param DccApplies $dcc_applies The DCC Applies helper.
|
* @param DccApplies $dcc_applies The DCC Applies helper.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct( DccApplies $dcc_applies ) {
|
||||||
DccApplies $dcc_applies
|
|
||||||
) {
|
|
||||||
$this->dcc_applies = $dcc_applies;
|
$this->dcc_applies = $dcc_applies;
|
||||||
$this->products = array(
|
|
||||||
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new copy of this object with the given value set.
|
|
||||||
*
|
|
||||||
* @param string[] $products The list of products ('PPCP', 'EXPRESS_CHECKOUT').
|
|
||||||
* @return static
|
|
||||||
*/
|
|
||||||
public function with_products( array $products ): self {
|
|
||||||
$obj = clone $this;
|
|
||||||
|
|
||||||
$obj->products = $products;
|
|
||||||
|
|
||||||
return $obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,82 +40,119 @@ class PartnerReferralsData {
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function nonce(): string {
|
public function nonce() : string {
|
||||||
return 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
|
return 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data.
|
* Returns the data.
|
||||||
*
|
*
|
||||||
|
* @param string[] $products The list of products to use ('PPCP', 'EXPRESS_CHECKOUT').
|
||||||
|
* Default is based on DCC availability.
|
||||||
|
* @param string $onboarding_token A security token to finalize the onboarding process.
|
||||||
|
* @param bool $use_subscriptions If the merchant requires subscription features.
|
||||||
|
* @param bool $use_card_payments If the merchant wants to process credit card payments.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function data(): array {
|
public function data( array $products = array(), string $onboarding_token = '', bool $use_subscriptions = null, bool $use_card_payments = true ) : array {
|
||||||
|
if ( ! $products ) {
|
||||||
|
$products = array(
|
||||||
|
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the partners referrals data.
|
* Filter the return-URL, which is called at the end of the OAuth onboarding
|
||||||
|
* process, when the merchant clicks the "Return to your shop" button.
|
||||||
*/
|
*/
|
||||||
return apply_filters(
|
$return_url = apply_filters(
|
||||||
'ppcp_partner_referrals_data',
|
'woocommerce_paypal_payments_partner_config_override_return_url',
|
||||||
array(
|
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' )
|
||||||
'partner_config_override' => array(
|
);
|
||||||
/**
|
|
||||||
* Returns the URL which will be opened at the end of onboarding.
|
/**
|
||||||
*/
|
* Filter the label of the "Return to your shop" button.
|
||||||
'return_url' => apply_filters(
|
* It's displayed on the very last page of the onboarding popup.
|
||||||
'woocommerce_paypal_payments_partner_config_override_return_url',
|
*/
|
||||||
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' )
|
$return_url_label = apply_filters(
|
||||||
),
|
'woocommerce_paypal_payments_partner_config_override_return_url_description',
|
||||||
/**
|
__( 'Return to your shop.', 'woocommerce-paypal-payments' )
|
||||||
* Returns the description of the URL which will be opened at the end of onboarding.
|
);
|
||||||
*/
|
|
||||||
'return_url_description' => apply_filters(
|
$capabilities = array();
|
||||||
'woocommerce_paypal_payments_partner_config_override_return_url_description',
|
$first_party_features = array(
|
||||||
__( 'Return to your shop.', 'woocommerce-paypal-payments' )
|
'PAYMENT',
|
||||||
),
|
'REFUND',
|
||||||
'show_add_credit_card' => true,
|
'ADVANCED_TRANSACTIONS_SEARCH',
|
||||||
|
'TRACKING_SHIPMENT_READWRITE',
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( true === $use_subscriptions ) {
|
||||||
|
$capabilities[] = 'PAYPAL_WALLET_VAULTING_ADVANCED';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility. Keep those features in the legacy UI (null-value).
|
||||||
|
// Move this into the previous condition, once legacy code is removed.
|
||||||
|
if ( false !== $use_subscriptions ) {
|
||||||
|
$first_party_features[] = 'FUTURE_PAYMENT';
|
||||||
|
$first_party_features[] = 'VAULT';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( false === $use_subscriptions ) {
|
||||||
|
// Only use "ADVANCED_VAULTING" product for onboarding with subscriptions.
|
||||||
|
$products = array_filter(
|
||||||
|
$products,
|
||||||
|
static fn( $product ) => $product !== 'ADVANCED_VAULTING'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = array(
|
||||||
|
'partner_config_override' => array(
|
||||||
|
'return_url' => $return_url,
|
||||||
|
'return_url_description' => $return_url_label,
|
||||||
|
'show_add_credit_card' => $use_card_payments,
|
||||||
|
),
|
||||||
|
'products' => $products,
|
||||||
|
'capabilities' => $capabilities,
|
||||||
|
'legal_consents' => array(
|
||||||
|
array(
|
||||||
|
'type' => 'SHARE_DATA_CONSENT',
|
||||||
|
'granted' => true,
|
||||||
),
|
),
|
||||||
'products' => $this->products,
|
),
|
||||||
'legal_consents' => array(
|
'operations' => array(
|
||||||
array(
|
array(
|
||||||
'type' => 'SHARE_DATA_CONSENT',
|
'operation' => 'API_INTEGRATION',
|
||||||
'granted' => true,
|
'api_integration_preference' => array(
|
||||||
),
|
'rest_api_integration' => array(
|
||||||
),
|
'integration_method' => 'PAYPAL',
|
||||||
'operations' => array(
|
'integration_type' => 'FIRST_PARTY',
|
||||||
array(
|
'first_party_details' => array(
|
||||||
'operation' => 'API_INTEGRATION',
|
'features' => $first_party_features,
|
||||||
'api_integration_preference' => array(
|
'seller_nonce' => $this->nonce(),
|
||||||
'rest_api_integration' => array(
|
|
||||||
'integration_method' => 'PAYPAL',
|
|
||||||
'integration_type' => 'FIRST_PARTY',
|
|
||||||
'first_party_details' => array(
|
|
||||||
'features' => array(
|
|
||||||
'PAYMENT',
|
|
||||||
'FUTURE_PAYMENT',
|
|
||||||
'REFUND',
|
|
||||||
'ADVANCED_TRANSACTIONS_SEARCH',
|
|
||||||
'VAULT',
|
|
||||||
'TRACKING_SHIPMENT_READWRITE',
|
|
||||||
),
|
|
||||||
'seller_nonce' => $this->nonce(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append the validation token to the return_url
|
* Filter the final partners referrals data collection.
|
||||||
*
|
*/
|
||||||
* @param array $data The referral data.
|
$payload = apply_filters( 'ppcp_partner_referrals_data', $payload );
|
||||||
* @param string $token The token to be appended.
|
|
||||||
* @return array
|
// An empty array is not permitted.
|
||||||
*/
|
if ( isset( $payload['capabilities'] ) && ! $payload['capabilities'] ) {
|
||||||
public function append_onboarding_token( array $data, string $token ): array {
|
unset( $payload['capabilities'] );
|
||||||
$data['partner_config_override']['return_url'] =
|
}
|
||||||
add_query_arg( 'ppcpToken', $token, $data['partner_config_override']['return_url'] );
|
|
||||||
return $data;
|
// Add the nonce in the end, to maintain backwards compatibility of filters.
|
||||||
|
$payload['partner_config_override']['return_url'] = add_query_arg(
|
||||||
|
array( 'ppcpToken' => $onboarding_token ),
|
||||||
|
$payload['partner_config_override']['return_url']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,12 +103,8 @@ class OnboardingRenderer {
|
||||||
'displayMode' => 'minibrowser',
|
'displayMode' => 'minibrowser',
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = $this->partner_referrals_data
|
|
||||||
->with_products( $products )
|
|
||||||
->data();
|
|
||||||
|
|
||||||
$environment = $is_production ? 'production' : 'sandbox';
|
$environment = $is_production ? 'production' : 'sandbox';
|
||||||
$product = 'PPCP' === $data['products'][0] ? 'ppcp' : 'express_checkout';
|
$product = strtolower( $products[0] ?? 'express_checkout' );
|
||||||
$cache_key = $environment . '-' . $product;
|
$cache_key = $environment . '-' . $product;
|
||||||
|
|
||||||
$onboarding_url = new OnboardingUrl( $this->cache, $cache_key, get_current_user_id() );
|
$onboarding_url = new OnboardingUrl( $this->cache, $cache_key, get_current_user_id() );
|
||||||
|
@ -122,8 +118,7 @@ class OnboardingRenderer {
|
||||||
|
|
||||||
$onboarding_url->init();
|
$onboarding_url->init();
|
||||||
|
|
||||||
$data = $this->partner_referrals_data
|
$data = $this->partner_referrals_data->data( $products, $onboarding_url->token() ?: '' );
|
||||||
->append_onboarding_token( $data, $onboarding_url->token() ?: '' );
|
|
||||||
|
|
||||||
$url = $is_production ? $this->production_partner_referrals->signup_link( $data ) : $this->sandbox_partner_referrals->signup_link( $data );
|
$url = $is_production ? $this->production_partner_referrals->signup_link( $data ) : $this->sandbox_partner_referrals->signup_link( $data );
|
||||||
$url = add_query_arg( $args, $url );
|
$url = add_query_arg( $args, $url );
|
||||||
|
|
|
@ -68,10 +68,11 @@ export function sandboxOnboardingUrl() {
|
||||||
/**
|
/**
|
||||||
* Side effect. Fetches the ISU-login URL for a production account.
|
* Side effect. Fetches the ISU-login URL for a production account.
|
||||||
*
|
*
|
||||||
* @param {string[]} products Which products/features to display in the ISU popup.
|
* @param {string[]} [products=[]] Which products/features to display in the ISU popup.
|
||||||
|
* @param {Object} [options={}] Options to customize the onboarding workflow.
|
||||||
* @return {Function} The thunk function.
|
* @return {Function} The thunk function.
|
||||||
*/
|
*/
|
||||||
export function productionOnboardingUrl( products = [] ) {
|
export function productionOnboardingUrl( products = [], options = {} ) {
|
||||||
return async () => {
|
return async () => {
|
||||||
try {
|
try {
|
||||||
return apiFetch( {
|
return apiFetch( {
|
||||||
|
@ -80,6 +81,7 @@ export function productionOnboardingUrl( products = [] ) {
|
||||||
data: {
|
data: {
|
||||||
useSandbox: false,
|
useSandbox: false,
|
||||||
products,
|
products,
|
||||||
|
options,
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
|
|
|
@ -41,8 +41,6 @@ const useHooks = () => {
|
||||||
const { useTransient, usePersistent, dispatch, select } = useStoreData();
|
const { useTransient, usePersistent, dispatch, select } = useStoreData();
|
||||||
const {
|
const {
|
||||||
persist,
|
persist,
|
||||||
sandboxOnboardingUrl,
|
|
||||||
productionOnboardingUrl,
|
|
||||||
authenticateWithCredentials,
|
authenticateWithCredentials,
|
||||||
authenticateWithOAuth,
|
authenticateWithOAuth,
|
||||||
startWebhookSimulation,
|
startWebhookSimulation,
|
||||||
|
@ -53,7 +51,6 @@ const useHooks = () => {
|
||||||
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
|
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
|
||||||
|
|
||||||
// Persistent accessors.
|
// Persistent accessors.
|
||||||
const [ isSandboxMode, setSandboxMode ] = usePersistent( 'useSandbox' );
|
|
||||||
const [ isManualConnectionMode, setManualConnectionMode ] = usePersistent(
|
const [ isManualConnectionMode, setManualConnectionMode ] = usePersistent(
|
||||||
'useManualConnection'
|
'useManualConnection'
|
||||||
);
|
);
|
||||||
|
@ -71,16 +68,10 @@ const useHooks = () => {
|
||||||
return {
|
return {
|
||||||
activeModal,
|
activeModal,
|
||||||
setActiveModal,
|
setActiveModal,
|
||||||
isSandboxMode,
|
|
||||||
setSandboxMode: ( state ) => {
|
|
||||||
return savePersistent( setSandboxMode, state );
|
|
||||||
},
|
|
||||||
isManualConnectionMode,
|
isManualConnectionMode,
|
||||||
setManualConnectionMode: ( state ) => {
|
setManualConnectionMode: ( state ) => {
|
||||||
return savePersistent( setManualConnectionMode, state );
|
return savePersistent( setManualConnectionMode, state );
|
||||||
},
|
},
|
||||||
sandboxOnboardingUrl,
|
|
||||||
productionOnboardingUrl,
|
|
||||||
authenticateWithCredentials,
|
authenticateWithCredentials,
|
||||||
authenticateWithOAuth,
|
authenticateWithOAuth,
|
||||||
wooSettings,
|
wooSettings,
|
||||||
|
@ -105,13 +96,23 @@ export const useStore = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSandbox = () => {
|
export const useSandbox = () => {
|
||||||
const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks();
|
const { dispatch, usePersistent } = useStoreData();
|
||||||
|
const [ isSandboxMode, setSandboxMode ] = usePersistent( 'useSandbox' );
|
||||||
|
const { sandboxOnboardingUrl, persist } = dispatch;
|
||||||
|
|
||||||
return { isSandboxMode, setSandboxMode, sandboxOnboardingUrl };
|
return {
|
||||||
|
isSandboxMode,
|
||||||
|
setSandboxMode: ( state ) => {
|
||||||
|
setSandboxMode( state );
|
||||||
|
return persist();
|
||||||
|
},
|
||||||
|
sandboxOnboardingUrl,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProduction = () => {
|
export const useProduction = () => {
|
||||||
const { productionOnboardingUrl } = useHooks();
|
const { dispatch } = useStoreData();
|
||||||
|
const { productionOnboardingUrl } = dispatch;
|
||||||
|
|
||||||
return { productionOnboardingUrl };
|
return { productionOnboardingUrl };
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,3 +24,9 @@ export const PRODUCT_TYPES = {
|
||||||
PHYSICAL: 'physical',
|
PHYSICAL: 'physical',
|
||||||
SUBSCRIPTIONS: 'subscriptions',
|
SUBSCRIPTIONS: 'subscriptions',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PAYPAL_PRODUCTS = {
|
||||||
|
ACDC: 'PPCP',
|
||||||
|
BCDC: 'EXPRESS_CHECKOUT',
|
||||||
|
VAULTING: 'ADVANCED_VAULTING',
|
||||||
|
};
|
||||||
|
|
|
@ -19,10 +19,6 @@ const useHooks = () => {
|
||||||
|
|
||||||
// Read-only flags and derived state.
|
// Read-only flags and derived state.
|
||||||
const flags = useSelect( ( select ) => select( STORE_NAME ).flags(), [] );
|
const flags = useSelect( ( select ) => select( STORE_NAME ).flags(), [] );
|
||||||
const determineProducts = useSelect(
|
|
||||||
( select ) => select( STORE_NAME ).determineProducts(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Transient accessors.
|
// Transient accessors.
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
@ -80,7 +76,6 @@ const useHooks = () => {
|
||||||
);
|
);
|
||||||
return savePersistent( setProducts, validProducts );
|
return savePersistent( setProducts, validProducts );
|
||||||
},
|
},
|
||||||
determineProducts,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,9 +136,9 @@ export const useNavigationState = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDetermineProducts = () => {
|
export const useDetermineProducts = () => {
|
||||||
const { determineProducts } = useHooks();
|
return useSelect( ( select ) => {
|
||||||
|
return select( STORE_NAME ).determineProductsAndCaps();
|
||||||
return determineProducts;
|
}, [] );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFlags = () => {
|
export const useFlags = () => {
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
* @file
|
* @file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { PAYPAL_PRODUCTS, PRODUCT_TYPES } from './configuration';
|
||||||
|
|
||||||
const EMPTY_OBJ = Object.freeze( {} );
|
const EMPTY_OBJ = Object.freeze( {} );
|
||||||
|
|
||||||
const getState = ( state ) => state || EMPTY_OBJ;
|
const getState = ( state ) => state || EMPTY_OBJ;
|
||||||
|
@ -25,44 +27,71 @@ export const flags = ( state ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the products that we use for the production login link in the last onboarding step.
|
* Returns details about products and capabilities to use for the production login link in
|
||||||
|
* the last onboarding step.
|
||||||
*
|
*
|
||||||
* This selector does not return state-values, but uses the state to derive the products-array
|
* This selector does not return state-values, but uses the state to derive the products-array
|
||||||
* that should be returned.
|
* that should be returned.
|
||||||
*
|
*
|
||||||
* @param {{}} state
|
* @param {{}} state
|
||||||
* @return {string[]} The ISU products, based on choices made in the onboarding wizard.
|
* @return {{products:string[], options:{}}} The ISU products, based on choices made in the onboarding wizard.
|
||||||
*/
|
*/
|
||||||
export const determineProducts = ( state ) => {
|
export const determineProductsAndCaps = ( state ) => {
|
||||||
const derivedProducts = [];
|
/**
|
||||||
|
* An array of product-names that are used to build an onboarding URL via the
|
||||||
|
* PartnerReferrals API. To avoid confusion with the "products" property from the
|
||||||
|
* Redux store, this collection has a distinct name.
|
||||||
|
*
|
||||||
|
* On server-side, this value is referred to as "products" again.
|
||||||
|
*/
|
||||||
|
const apiModules = [];
|
||||||
|
|
||||||
const { isCasualSeller, areOptionalPaymentMethodsEnabled } =
|
/**
|
||||||
|
* Internal options that are parsed by the PartnerReferrals class to customize
|
||||||
|
* the API payload.
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
useSubscriptions: false,
|
||||||
|
useCardPayments: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isCasualSeller, areOptionalPaymentMethodsEnabled, products } =
|
||||||
persistentData( state );
|
persistentData( state );
|
||||||
const { canUseVaulting, canUseCardPayments } = flags( state );
|
const { canUseVaulting, canUseCardPayments } = flags( state );
|
||||||
|
const cardPaymentsEligibleAndSelected =
|
||||||
|
canUseCardPayments && areOptionalPaymentMethodsEnabled;
|
||||||
|
|
||||||
if ( ! canUseCardPayments || ! areOptionalPaymentMethodsEnabled ) {
|
if ( ! cardPaymentsEligibleAndSelected ) {
|
||||||
/**
|
/**
|
||||||
* Branch 1: Credit Card Payments not available.
|
* Branch 1: Credit Card Payments not available.
|
||||||
* The store uses the Express-checkout product.
|
* The store uses the Express-checkout product.
|
||||||
*/
|
*/
|
||||||
derivedProducts.push( 'EXPRESS_CHECKOUT' );
|
apiModules.push( PAYPAL_PRODUCTS.BCDC );
|
||||||
} else if ( isCasualSeller ) {
|
} else if ( isCasualSeller ) {
|
||||||
/**
|
/**
|
||||||
* Branch 2: Merchant has no business.
|
* Branch 2: Merchant has no business.
|
||||||
* The store uses the Express-checkout product.
|
* The store uses the Express-checkout product.
|
||||||
*/
|
*/
|
||||||
derivedProducts.push( 'EXPRESS_CHECKOUT' );
|
apiModules.push( PAYPAL_PRODUCTS.BCDC );
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* Branch 3: Merchant is business, and can use CC payments.
|
* Branch 3: Merchant is business, and can use CC payments.
|
||||||
* The store uses the advanced PPCP product.
|
* The store uses the advanced PPCP product.
|
||||||
|
*
|
||||||
|
* This is the only branch that can use subscriptions.
|
||||||
*/
|
*/
|
||||||
derivedProducts.push( 'PPCP' );
|
apiModules.push( PAYPAL_PRODUCTS.ACDC );
|
||||||
|
|
||||||
|
if ( products?.includes( PRODUCT_TYPES.SUBSCRIPTIONS ) ) {
|
||||||
|
options.useSubscriptions = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( canUseVaulting ) {
|
||||||
|
apiModules.push( PAYPAL_PRODUCTS.VAULTING );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( canUseVaulting ) {
|
options.useCardPayments = cardPaymentsEligibleAndSelected;
|
||||||
derivedProducts.push( 'ADVANCED_VAULTING' );
|
|
||||||
}
|
|
||||||
|
|
||||||
return derivedProducts;
|
return { products: apiModules, options };
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
import { PRODUCT_TYPES } from './configuration';
|
||||||
|
import { determineProductsAndCaps } from './selectors';
|
||||||
|
|
||||||
|
describe( 'determineProductsAndCaps selector [casual seller]', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT when card payments are not available',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: true,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: false, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT when optional payment methods are disabled',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: true,
|
||||||
|
areOptionalPaymentMethodsEnabled: false,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT for casual sellers with card payments',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: true,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT and ADVANCED_VAULTING when card payments are not available but vaulting is',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: true,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: false, canUseVaulting: true },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should ignore SUBSCRIPTION product for casual sellers',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: true,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
products: [ PRODUCT_TYPES.SUBSCRIPTIONS ],
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: false, canUseVaulting: true },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each( testCases )( '$name', ( { state, expected } ) => {
|
||||||
|
const result = determineProductsAndCaps( state );
|
||||||
|
expect( result ).toEqual( expected );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'determineProductsAndCaps selector [business seller]', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT when card payments are not available',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: false, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT when optional payment methods are disabled',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: false,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return PPCP for business merchants with card payments',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: false },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'PPCP' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should include ADVANCED_VAULTING when vaulting is available',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: true },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'PPCP', 'ADVANCED_VAULTING' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return EXPRESS_CHECKOUT and ADVANCED_VAULTING when card payments are not available but vaulting is',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
products: [ PRODUCT_TYPES.VIRTUAL ],
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: false, canUseVaulting: true },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
options: { useSubscriptions: false, useCardPayments: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should enable the SUBSCRIPTIONS option when a business seller selects the subscriptions-product',
|
||||||
|
state: {
|
||||||
|
data: {
|
||||||
|
isCasualSeller: false,
|
||||||
|
areOptionalPaymentMethodsEnabled: true,
|
||||||
|
products: [ PRODUCT_TYPES.SUBSCRIPTIONS ],
|
||||||
|
},
|
||||||
|
flags: { canUseCardPayments: true, canUseVaulting: true },
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
products: [ 'PPCP', 'ADVANCED_VAULTING' ],
|
||||||
|
options: { useSubscriptions: true, useCardPayments: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each( testCases )( '$name', ( { state, expected } ) => {
|
||||||
|
const result = determineProductsAndCaps( state );
|
||||||
|
expect( result ).toEqual( expected );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -30,7 +30,7 @@ const ACTIVITIES = {
|
||||||
export const useHandleOnboardingButton = ( isSandbox ) => {
|
export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
|
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
|
||||||
const { productionOnboardingUrl } = CommonHooks.useProduction();
|
const { productionOnboardingUrl } = CommonHooks.useProduction();
|
||||||
const products = OnboardingHooks.useDetermineProducts();
|
const { products, options } = OnboardingHooks.useDetermineProducts();
|
||||||
const { startActivity } = CommonHooks.useBusyState();
|
const { startActivity } = CommonHooks.useBusyState();
|
||||||
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
||||||
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
|
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
|
||||||
|
@ -43,7 +43,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
if ( isSandbox ) {
|
if ( isSandbox ) {
|
||||||
res = await sandboxOnboardingUrl();
|
res = await sandboxOnboardingUrl();
|
||||||
} else {
|
} else {
|
||||||
res = await productionOnboardingUrl( products );
|
res = await productionOnboardingUrl( products, options );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( res.success && res.data ) {
|
if ( res.success && res.data ) {
|
||||||
|
|
|
@ -82,6 +82,16 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
return array_map( 'sanitize_text_field', $products );
|
return array_map( 'sanitize_text_field', $products );
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
'options' => array(
|
||||||
|
'requires' => false,
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'bool',
|
||||||
|
),
|
||||||
|
'sanitize_callback' => function ( $flags ) {
|
||||||
|
return array_map( array( $this, 'to_boolean' ), $flags );
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -97,9 +107,10 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
public function get_login_url( WP_REST_Request $request ) : WP_REST_Response {
|
public function get_login_url( WP_REST_Request $request ) : WP_REST_Response {
|
||||||
$use_sandbox = $request->get_param( 'useSandbox' );
|
$use_sandbox = $request->get_param( 'useSandbox' );
|
||||||
$products = $request->get_param( 'products' );
|
$products = $request->get_param( 'products' );
|
||||||
|
$flags = (array) $request->get_param( 'options' );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$url = $this->url_generator->generate( $products, $use_sandbox );
|
$url = $this->url_generator->generate( $products, $flags, $use_sandbox );
|
||||||
|
|
||||||
return $this->return_success( $url );
|
return $this->return_success( $url );
|
||||||
} catch ( \Exception $e ) {
|
} catch ( \Exception $e ) {
|
||||||
|
|
|
@ -82,12 +82,13 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* @param array $products An array of product identifiers to include in the sign-up process.
|
* @param array $products An array of product identifiers to include in the sign-up process.
|
||||||
* These determine the PayPal onboarding experience.
|
* These determine the PayPal onboarding experience.
|
||||||
|
* @param array $flags Onboarding choices that will customize the ISU payload.
|
||||||
* @param bool $use_sandbox Whether to generate a sandbox URL.
|
* @param bool $use_sandbox Whether to generate a sandbox URL.
|
||||||
*
|
*
|
||||||
* @return string The generated PayPal onboarding URL.
|
* @return string The generated PayPal onboarding URL.
|
||||||
*/
|
*/
|
||||||
public function generate( array $products = array(), bool $use_sandbox = false ) : string {
|
public function generate( array $products = array(), array $flags = array(), bool $use_sandbox = false ) : string {
|
||||||
$cache_key = $this->cache_key( $products, $use_sandbox );
|
$cache_key = $this->cache_key( $products, $flags, $use_sandbox );
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
$onboarding_url = $this->url_manager->get( $cache_key, $user_id );
|
$onboarding_url = $this->url_manager->get( $cache_key, $user_id );
|
||||||
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
|
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
|
||||||
|
@ -100,7 +101,7 @@ class ConnectionUrlGenerator {
|
||||||
|
|
||||||
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
||||||
|
|
||||||
$url = $this->generate_new_url( $use_sandbox, $products, $onboarding_url, $cache_key );
|
$url = $this->generate_new_url( $use_sandbox, $products, $flags, $onboarding_url, $cache_key );
|
||||||
|
|
||||||
if ( $url ) {
|
if ( $url ) {
|
||||||
$this->persist_url( $onboarding_url, $url );
|
$this->persist_url( $onboarding_url, $url );
|
||||||
|
@ -112,18 +113,28 @@ class ConnectionUrlGenerator {
|
||||||
/**
|
/**
|
||||||
* Generates a cache key from the environment and sorted product array.
|
* Generates a cache key from the environment and sorted product array.
|
||||||
*
|
*
|
||||||
|
* Q: Why do we cache the connection URL?
|
||||||
|
* A: The URL is generated by a partner-referrals API, i.e. it requires a
|
||||||
|
* remote request; caching the response avoids unnecessary API calls.
|
||||||
|
*
|
||||||
* @param array $products Product identifiers that are part of the cache key.
|
* @param array $products Product identifiers that are part of the cache key.
|
||||||
|
* @param array $flags Onboarding flags.
|
||||||
* @param bool $for_sandbox Whether the cache contains a sandbox URL.
|
* @param bool $for_sandbox Whether the cache contains a sandbox URL.
|
||||||
*
|
*
|
||||||
* @return string The cache key, defining the product list and environment.
|
* @return string The cache key, defining the product list and environment.
|
||||||
*/
|
*/
|
||||||
protected function cache_key( array $products, bool $for_sandbox ) : string {
|
protected function cache_key( array $products, array $flags, bool $for_sandbox ) : string {
|
||||||
$environment = $for_sandbox ? 'sandbox' : 'production';
|
$environment = $for_sandbox ? 'sandbox' : 'production';
|
||||||
|
|
||||||
// Sort products alphabetically, to improve cache implementation.
|
// Sort products alphabetically, to improve cache implementation.
|
||||||
sort( $products );
|
sort( $products );
|
||||||
|
|
||||||
return $environment . '-' . implode( '-', $products );
|
// Extract the names of active flags.
|
||||||
|
$active_flags = array_keys( array_filter( $flags ) );
|
||||||
|
|
||||||
|
return strtolower(
|
||||||
|
$environment . '-' . implode( '-', $products ) . '-' . implode( '-', $active_flags )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,12 +173,13 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* @param bool $for_sandbox Whether to generate a sandbox URL.
|
* @param bool $for_sandbox Whether to generate a sandbox URL.
|
||||||
* @param array $products The products array.
|
* @param array $products The products array.
|
||||||
|
* @param array $flags Onboarding flags.
|
||||||
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
||||||
* @param string $cache_key The cache key.
|
* @param string $cache_key The cache key.
|
||||||
*
|
*
|
||||||
* @return string The generated URL or an empty string on failure.
|
* @return string The generated URL or an empty string on failure.
|
||||||
*/
|
*/
|
||||||
protected function generate_new_url( bool $for_sandbox, array $products, OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
protected function generate_new_url( bool $for_sandbox, array $products, array $flags, OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
||||||
$query_args = array( 'displayMode' => 'minibrowser' );
|
$query_args = array( 'displayMode' => 'minibrowser' );
|
||||||
$onboarding_url->init();
|
$onboarding_url->init();
|
||||||
|
|
||||||
|
@ -179,7 +191,7 @@ class ConnectionUrlGenerator {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->prepare_referral_data( $products, $onboarding_token );
|
$data = $this->prepare_referral_data( $products, $flags, $onboarding_token );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$referral = $this->partner_referrals->get_value( $for_sandbox );
|
$referral = $this->partner_referrals->get_value( $for_sandbox );
|
||||||
|
@ -197,16 +209,18 @@ class ConnectionUrlGenerator {
|
||||||
* Prepares the referral data.
|
* Prepares the referral data.
|
||||||
*
|
*
|
||||||
* @param array $products The products array.
|
* @param array $products The products array.
|
||||||
|
* @param array $flags Onboarding flags.
|
||||||
* @param string $onboarding_token The onboarding token.
|
* @param string $onboarding_token The onboarding token.
|
||||||
*
|
*
|
||||||
* @return array The prepared referral data.
|
* @return array The prepared referral data.
|
||||||
*/
|
*/
|
||||||
protected function prepare_referral_data( array $products, string $onboarding_token ) : array {
|
protected function prepare_referral_data( array $products, array $flags, string $onboarding_token ) : array {
|
||||||
$data = $this->referrals_data
|
return $this->referrals_data->data(
|
||||||
->with_products( $products )
|
$products,
|
||||||
->data();
|
$onboarding_token,
|
||||||
|
(bool) ( $flags['useSubscriptions'] ?? false ),
|
||||||
return $this->referrals_data->append_onboarding_token( $data, $onboarding_token );
|
(bool) ( $flags['useCardPayments'] ?? false )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
217
tests/PHPUnit/ApiClient/Repository/PartnerReferralsDataTest.php
Normal file
217
tests/PHPUnit/ApiClient/Repository/PartnerReferralsDataTest.php
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
<?php
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\ApiClient\Repository;
|
||||||
|
|
||||||
|
use Mockery;
|
||||||
|
use WooCommerce\PayPalCommerce\TestCase;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||||
|
use function Brain\Monkey\Functions\when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group api
|
||||||
|
* @group onboarding
|
||||||
|
*/
|
||||||
|
class PartnerReferralsDataTest extends TestCase {
|
||||||
|
/**
|
||||||
|
* Expected nonce that should appear in the payload.
|
||||||
|
*/
|
||||||
|
private const DEFAULT_NONCE = 'a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample URL which is used to mock the `admin_url()` response, used to build the return URL.
|
||||||
|
* Specifically, we want to verify the $path which is appended to the admin URL.
|
||||||
|
*/
|
||||||
|
private const ADMIN_URL = 'https://example.com/wp-admin/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sample token that we add to the return URL.
|
||||||
|
* We pass this const to the `->data()` method to ensure it's appended at the end of the
|
||||||
|
* return URL as-is.
|
||||||
|
*/
|
||||||
|
private const TOKEN = 'SECURE_TOKEN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected return URL to see at in the payload, including the ppcpToken.
|
||||||
|
*/
|
||||||
|
private const RETURN_URL = 'https://example.com/wp-admin/admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcpToken=SECURE_TOKEN';
|
||||||
|
|
||||||
|
private $testee;
|
||||||
|
private $dccApplies;
|
||||||
|
|
||||||
|
public function setUp() : void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->dccApplies = Mockery::mock( DccApplies::class );
|
||||||
|
$this->testee = new PartnerReferralsData( $this->dccApplies );
|
||||||
|
|
||||||
|
when( 'admin_url' )->alias( static fn( string $path ) => self::ADMIN_URL . $path );
|
||||||
|
when( 'add_query_arg' )->justReturn( self::RETURN_URL );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base structure of the API payload. Each test should modify the returned
|
||||||
|
* value of the method to meet its expectations.
|
||||||
|
*
|
||||||
|
* This avoids repeating the full structure, while also highlighting the
|
||||||
|
* specific changes that different params will generate.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getBaseExpectedArray() : array {
|
||||||
|
return [
|
||||||
|
'partner_config_override' => [
|
||||||
|
'return_url' => self::RETURN_URL,
|
||||||
|
'return_url_description' => 'Return to your shop.',
|
||||||
|
'show_add_credit_card' => true,
|
||||||
|
],
|
||||||
|
'legal_consents' => [
|
||||||
|
[
|
||||||
|
'type' => 'SHARE_DATA_CONSENT',
|
||||||
|
'granted' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'operations' => [
|
||||||
|
[
|
||||||
|
'operation' => 'API_INTEGRATION',
|
||||||
|
'api_integration_preference' => [
|
||||||
|
'rest_api_integration' => [
|
||||||
|
'integration_method' => 'PAYPAL',
|
||||||
|
'integration_type' => 'FIRST_PARTY',
|
||||||
|
'first_party_details' => [
|
||||||
|
'features' => [
|
||||||
|
'PAYMENT',
|
||||||
|
'REFUND',
|
||||||
|
'ADVANCED_TRANSACTIONS_SEARCH',
|
||||||
|
'TRACKING_SHIPMENT_READWRITE',
|
||||||
|
],
|
||||||
|
'seller_nonce' => self::DEFAULT_NONCE,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider for testing flag combinations.
|
||||||
|
*
|
||||||
|
* @return array[] Test cases with [has_subscriptions, has_cards, expected_changes]
|
||||||
|
*/
|
||||||
|
public function flagCombinationsProvider() : array {
|
||||||
|
return [
|
||||||
|
'with subscriptions and cards' => [
|
||||||
|
true, // With subscription?
|
||||||
|
true, // With cards?
|
||||||
|
[
|
||||||
|
'capabilities' => [ 'PAYPAL_WALLET_VAULTING_ADVANCED' ],
|
||||||
|
'show_add_credit_card' => true,
|
||||||
|
'has_vault_features' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'with subscriptions, no cards' => [
|
||||||
|
true, // With subscription?
|
||||||
|
false, // With cards?
|
||||||
|
[
|
||||||
|
'capabilities' => [ 'PAYPAL_WALLET_VAULTING_ADVANCED' ],
|
||||||
|
'show_add_credit_card' => false,
|
||||||
|
'has_vault_features' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'no subscriptions, with cards' => [
|
||||||
|
false, // With subscription?
|
||||||
|
true, // With cards?
|
||||||
|
[
|
||||||
|
'show_add_credit_card' => true,
|
||||||
|
'has_vault_features' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'no subscriptions, no cards' => [
|
||||||
|
false, // With subscription?
|
||||||
|
false, // With cards?
|
||||||
|
[
|
||||||
|
'show_add_credit_card' => false,
|
||||||
|
'has_vault_features' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the default "products" are derived from the DccApplies response.
|
||||||
|
*/
|
||||||
|
public function testDefaultValues() : void {
|
||||||
|
/**
|
||||||
|
* Case 1: The data() method gets no parameters, and the DccApplies check
|
||||||
|
* returns TRUE. Onboarding payload should indicate "PPCP".
|
||||||
|
*/
|
||||||
|
$this->dccApplies->expects( 'for_country_currency' )->andReturn( true );
|
||||||
|
$result = $this->testee->data();
|
||||||
|
$this->assertEquals( [ 'PPCP' ], $result['products'] );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case 2: The data() method gets no parameters, and the DccApplies check
|
||||||
|
* returns FALSE. Onboarding payload should indicate "EXPRESS_CHECKOUT".
|
||||||
|
*/
|
||||||
|
$this->dccApplies->expects( 'for_country_currency' )->andReturn( false );
|
||||||
|
$result = $this->testee->data();
|
||||||
|
$this->assertEquals( [ 'EXPRESS_CHECKOUT' ], $result['products'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the generated API payload is stable and contains the expected values.
|
||||||
|
*
|
||||||
|
* The test only verifies the "products" and "token" arguments, as those are the
|
||||||
|
* core params present in the legacy and new UI.
|
||||||
|
*/
|
||||||
|
public function testDataStructure() : void {
|
||||||
|
/**
|
||||||
|
* Undefined subscription: Keep vaulting in first-party, but don't add the capability.
|
||||||
|
*/
|
||||||
|
$result = $this->testee->data( [ 'PPCP' ], self::TOKEN );
|
||||||
|
$this->dccApplies->shouldNotHaveReceived( 'for_country_currency' );
|
||||||
|
|
||||||
|
$expected = $this->getBaseExpectedArray();
|
||||||
|
|
||||||
|
$expected['products'] = [ 'PPCP' ];
|
||||||
|
|
||||||
|
$expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'][] = 'FUTURE_PAYMENT';
|
||||||
|
$expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'][] = 'VAULT';
|
||||||
|
|
||||||
|
$this->assertArrayNotHasKey( 'capabilities', $expected );
|
||||||
|
$this->assertEquals( $expected, $result );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test how different flag combinations affect the data structure.
|
||||||
|
* Those flags are present in the new UI.
|
||||||
|
*
|
||||||
|
* @dataProvider flagCombinationsProvider
|
||||||
|
*/
|
||||||
|
public function testDataStructureWithFlags( bool $has_subscriptions, bool $has_cards, array $expected_changes ) : void {
|
||||||
|
$result = $this->testee->data( [ 'PPCP' ], self::TOKEN, $has_subscriptions, $has_cards );
|
||||||
|
$expected = $this->getBaseExpectedArray();
|
||||||
|
|
||||||
|
$expected['products'] = [ 'PPCP' ];
|
||||||
|
|
||||||
|
if ( isset( $expected_changes['capabilities'] ) ) {
|
||||||
|
$expected['capabilities'] = $expected_changes['capabilities'];
|
||||||
|
} else {
|
||||||
|
$this->assertArrayNotHasKey( 'capabilities', $expected );
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected['partner_config_override']['show_add_credit_card'] = $expected_changes['show_add_credit_card'];
|
||||||
|
|
||||||
|
if ( $expected_changes['has_vault_features'] ) {
|
||||||
|
$expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'][] = 'FUTURE_PAYMENT';
|
||||||
|
$expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'][] = 'VAULT';
|
||||||
|
} else {
|
||||||
|
// Double-check that the features are not present in our expected array
|
||||||
|
$this->assertNotContains( 'FUTURE_PAYMENT', $expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'] );
|
||||||
|
$this->assertNotContains( 'VAULT', $expected['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['features'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals( $expected, $result );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue