Skip to content

Commit

Permalink
Merge pull request #53 from woocommerce/feature/cash-app-pay
Browse files Browse the repository at this point in the history
Add support for Cash App Pay payment method
  • Loading branch information
vikrampm1 committed Jan 31, 2024
2 parents 58c7938 + 83c6f57 commit 7c8d0f5
Show file tree
Hide file tree
Showing 24 changed files with 3,091 additions and 96 deletions.
184 changes: 184 additions & 0 deletions assets/blocks/cash-app-pay/component-cash-app-pay.js
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>
)}
</>
);
};
1 change: 1 addition & 0 deletions assets/blocks/cash-app-pay/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PAYMENT_METHOD_ID = 'square_cash_app_pay';
81 changes: 81 additions & 0 deletions assets/blocks/cash-app-pay/index.js
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 );
Loading

0 comments on commit 7c8d0f5

Please sign in to comment.