-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from woocommerce/feature/cash-app-pay
Add support for Cash App Pay payment method
- Loading branch information
Showing
24 changed files
with
3,091 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useEffect, useState } from '@wordpress/element'; | ||
import { decodeEntities } from '@wordpress/html-entities'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
getSquareCashAppPayServerData, | ||
createPaymentRequest, | ||
setContinuationSession, | ||
log, | ||
} from './utils'; | ||
import { PAYMENT_METHOD_ID } from './constants'; | ||
|
||
const buttonId = 'wc-square-cash-app-pay'; | ||
|
||
/** | ||
* Square's credit card component | ||
* | ||
* @param {Object} props Incoming props | ||
*/ | ||
export const ComponentCashAppPay = (props) => { | ||
const [errorMessage, setErrorMessage] = useState(null); | ||
const [isLoaded, setIsLoaded] = useState(false); | ||
const [paymentNonce, setPaymentNonce] = useState(''); | ||
const { | ||
applicationId, | ||
locationId, | ||
buttonStyles, | ||
referenceId, | ||
generalError, | ||
gatewayIdDasherized, | ||
description, | ||
} = getSquareCashAppPayServerData(); | ||
const { | ||
onSubmit, | ||
emitResponse, | ||
eventRegistration, | ||
billing: { cartTotal, currency }, | ||
components: { LoadingMask }, | ||
activePaymentMethod, | ||
} = props; | ||
const { onPaymentSetup } = eventRegistration; | ||
|
||
// Checkout handler. | ||
useEffect(() => { | ||
const unsubscribe = onPaymentSetup(() => { | ||
if (!paymentNonce) { | ||
return { | ||
type: emitResponse.responseTypes.ERROR, | ||
message: generalError, | ||
}; | ||
} | ||
const paymentMethodData = { | ||
[`wc-${gatewayIdDasherized}-payment-nonce`]: paymentNonce || '', | ||
}; | ||
return { | ||
type: emitResponse.responseTypes.SUCCESS, | ||
meta: { | ||
paymentMethodData, | ||
}, | ||
}; | ||
}); | ||
return unsubscribe; | ||
}, [ | ||
emitResponse.responseTypes.SUCCESS, | ||
emitResponse.responseTypes.ERROR, | ||
onPaymentSetup, | ||
paymentNonce, | ||
]); | ||
|
||
// Initialize the Square Cash App Pay Button. | ||
useEffect(() => { | ||
setIsLoaded(false); | ||
setErrorMessage(null); | ||
// Bail if Square is not loaded. | ||
if (!window.Square) { | ||
return; | ||
} | ||
|
||
log('[Square Cash App Pay] Initializing Square Cash App Pay Button'); | ||
const payments = window.Square.payments(applicationId, locationId); | ||
if (!payments) { | ||
return; | ||
} | ||
|
||
async function setupIntegration(){ | ||
setIsLoaded(false); | ||
try { | ||
const paymentRequest = await createPaymentRequest(payments); | ||
if (window.wcSquareCashAppPay) { | ||
await window.wcSquareCashAppPay.destroy(); | ||
window.wcSquareCashAppPay = null; | ||
} | ||
|
||
const cashAppPay = await payments.cashAppPay(paymentRequest, { | ||
redirectURL: window.location.href, | ||
referenceId: referenceId, | ||
}); | ||
await cashAppPay.attach(`#${buttonId}`, buttonStyles); | ||
|
||
// Handle the payment response. | ||
cashAppPay.addEventListener('ontokenization', (event) => { | ||
const { tokenResult, error } = event.detail; | ||
if (error) { | ||
setPaymentNonce(''); | ||
setErrorMessage(error.message); | ||
} else if (tokenResult.status === 'OK') { | ||
const nonce = tokenResult.token; | ||
if (!nonce) { | ||
setPaymentNonce(''); | ||
setErrorMessage(generalError); | ||
} | ||
|
||
// Set the nonce. | ||
setPaymentNonce(nonce); | ||
|
||
// Place an Order. | ||
onSubmit(); | ||
} else { | ||
// Declined. Reset the nonce and re-initialize the Square Cash App Pay Button. | ||
setPaymentNonce(null); | ||
setupIntegration(); | ||
} | ||
}); | ||
|
||
// Handle the customer interaction. set continuation session to select the Cash App Pay payment method after the redirect back from the Cash App. | ||
cashAppPay.addEventListener('customerInteraction', (event) => { | ||
if (event.detail && event.detail.isMobile) { | ||
return setContinuationSession(); | ||
} | ||
}); | ||
|
||
window.wcSquareCashAppPay = cashAppPay; | ||
log('[Square Cash App Pay] Square Cash App Pay Button Loaded'); | ||
} catch (e) { | ||
setErrorMessage(generalError); | ||
console.error(e); | ||
} | ||
setIsLoaded(true); | ||
} | ||
setupIntegration(); | ||
|
||
return () => | ||
(async () => { | ||
if (window.wcSquareCashAppPay) { | ||
await window.wcSquareCashAppPay.destroy(); | ||
window.wcSquareCashAppPay = null; | ||
} | ||
})(); | ||
}, [cartTotal.value, currency.code]); | ||
|
||
// Disable the place order button when Cash App Pay is active. TODO: find a better way to do this. | ||
useEffect(() => { | ||
const button = document.querySelector( | ||
'button.wc-block-components-checkout-place-order-button' | ||
); | ||
if (button) { | ||
if (activePaymentMethod === PAYMENT_METHOD_ID && !paymentNonce) { | ||
button.setAttribute('disabled', 'disabled'); | ||
} | ||
return () => { | ||
button.removeAttribute('disabled'); | ||
}; | ||
} | ||
}, [activePaymentMethod, paymentNonce]); | ||
|
||
return ( | ||
<> | ||
<p>{decodeEntities(description || '')}</p> | ||
{errorMessage && ( | ||
<div className="woocommerce-error">{errorMessage}</div> | ||
)} | ||
{!errorMessage && ( | ||
<LoadingMask isLoading={!isLoaded} showSpinner={true}> | ||
<div id={buttonId}></div> | ||
</LoadingMask> | ||
)} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const PAYMENT_METHOD_ID = 'square_cash_app_pay'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { decodeEntities } from '@wordpress/html-entities'; | ||
import { registerPaymentMethod } from '@woocommerce/blocks-registry'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { ComponentCashAppPay } from './component-cash-app-pay'; | ||
import { PAYMENT_METHOD_ID } from './constants'; | ||
import { getSquareCashAppPayServerData, selectCashAppPaymentMethod } from './utils'; | ||
const { title, applicationId, locationId } = getSquareCashAppPayServerData(); | ||
|
||
/** | ||
* Label component | ||
* | ||
* @param {Object} props | ||
*/ | ||
const SquareCashAppPayLabel = (props) => { | ||
const { PaymentMethodLabel } = props.components; | ||
return <PaymentMethodLabel text={decodeEntities(title)} />; | ||
}; | ||
|
||
/** | ||
* Payment method content component | ||
* | ||
* @param {Object} props Incoming props for component (including props from Payments API) | ||
* @param {ComponentCashAppPay} props.RenderedComponent Component to render | ||
*/ | ||
const SquareComponent = ({ RenderedComponent, isEdit, ...props }) => { | ||
// Don't render anything if we're in the block editor. | ||
if (isEdit) { | ||
return null; | ||
} | ||
return <RenderedComponent {...props} />; | ||
}; | ||
|
||
/** | ||
* Square Cash App Pay payment method. | ||
*/ | ||
const squareCashAppPayMethod = { | ||
name: PAYMENT_METHOD_ID, | ||
label: <SquareCashAppPayLabel />, | ||
paymentMethodId: PAYMENT_METHOD_ID, | ||
ariaLabel: __( | ||
'Cash App Pay payment method', | ||
'woocommerce-square' | ||
), | ||
content: <SquareComponent RenderedComponent={ComponentCashAppPay} />, | ||
edit: <SquareComponent RenderedComponent={ComponentCashAppPay} isEdit={true} />, | ||
canMakePayment: ({ billingData, cartTotals }) => { | ||
const isSquareConnected = applicationId && locationId; | ||
const isCountrySupported = billingData.country === 'US'; | ||
const isCurrencySupported = cartTotals.currency_code === 'USD'; | ||
const isEnabled = isSquareConnected && isCountrySupported && isCurrencySupported; | ||
|
||
/** | ||
* Set the Cash App Pay payment method as active when the checkout form is rendered. | ||
* | ||
* TODO: Find a better way to do this. | ||
* Didn't find a suitable action to activate the cash app pay payment method when customer returns from the cash app. | ||
* | ||
* Initially tried to use the experimental__woocommerce_blocks-checkout-render-checkout-form action but it doesn't work when stripe payment gateway is enabled. | ||
*/ | ||
if ( isEnabled ) { | ||
selectCashAppPaymentMethod(); | ||
} | ||
|
||
return isEnabled; | ||
}, | ||
supports: { | ||
features: getSquareCashAppPayServerData().supports || [], | ||
showSavedCards: getSquareCashAppPayServerData().showSavedCards || false, | ||
showSaveOption: getSquareCashAppPayServerData().showSaveOption || false, | ||
} | ||
}; | ||
|
||
// Register Square Cash App. | ||
registerPaymentMethod( squareCashAppPayMethod ); |
Oops, something went wrong.