diff --git a/.size-limit b/.size-limit index 93092541..f9a54c73 100644 --- a/.size-limit +++ b/.size-limit @@ -24,6 +24,11 @@ "path": "dist/index.es.js", "import": "{ ApplePay, PaymentForm }" }, + { + "name": "`{ CashAppPay, PaymentForm }`", + "path": "dist/index.es.js", + "import": "{ CashAppPay, PaymentForm }" + }, { "name": "`{ CreditCard, PaymentForm }`", "path": "dist/index.es.js", @@ -40,13 +45,13 @@ "import": "{ GooglePay, PaymentForm }" }, { - "name": "`{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, GiftCard, GooglePay, PaymentForm }`", + "name": "`{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }`", "path": "dist/index.es.js", - "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, GiftCard, GooglePay, PaymentForm }" + "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }" }, { - "name": "Format: cjs, `{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, GiftCard, GooglePay, PaymentForm }`", + "name": "Format: cjs, `{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }`", "path": "dist/index.cjs.js", - "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, GiftCard, GooglePay, PaymentForm }" + "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }" } ] diff --git a/package.json b/package.json index c139b94a..58d7d874 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@size-limit/webpack-why": "^7.0.8", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", - "@types/node": "^17.0.34", + "@types/node": "^17.0.35", "@types/react": "^18.0.9", "@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/parser": "^5.25.0", @@ -75,7 +75,7 @@ "eslint": "^8.15.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react": "^7.30.0", "jsdom": "^19.0.0", "npm-run-all": "^4.1.5", "prettier": "^2.6.2", diff --git a/src/components/cash-app-pay/cash-app-pay.tsx b/src/components/cash-app-pay/cash-app-pay.tsx new file mode 100644 index 00000000..8c5bd44c --- /dev/null +++ b/src/components/cash-app-pay/cash-app-pay.tsx @@ -0,0 +1,97 @@ +// Dependencies +import * as React from 'react'; +import type * as Square from '@square/web-sdk'; + +// Internals +import { useForm } from '~/contexts/form'; +import type { CashAppPayProps } from './cash-app-pay.types'; + +function CashAppPay({ + callbacks, + id = 'rswps-cash-app-pay', + redirectURL, + referenceId, + shape, + size, + values, + width, + ...props +}: CashAppPayProps) { + const [cashApp, setCashApp] = React.useState(); + const { createPaymentRequest, payments } = useForm(); + + const paymentRequestOptions: Square.CashAppPaymentRequestOptions = React.useMemo( + () => ({ + redirectURL: redirectURL || window.location.href, + referenceId, + }), + [redirectURL, referenceId] + ); + + const options: Square.CashAppPayButtonOptions = React.useMemo( + () => ({ + shape, + size, + values, + width, + }), + [redirectURL, referenceId] + ); + + React.useEffect(() => { + if (!createPaymentRequest) { + throw new Error('`createPaymentRequest()` is required when using digital wallets'); + } + + const abortController = new AbortController(); + const { signal } = abortController; + + const start = async (signal: AbortSignal) => { + const paymentRequest = payments?.paymentRequest(createPaymentRequest); + + if (!paymentRequest) { + throw new Error('`paymentRequest` is required when using digital wallets'); + } + + try { + const cashApp = await payments?.cashAppPay(paymentRequest, paymentRequestOptions).then((res) => { + if (signal?.aborted) { + return; + } + + setCashApp(res); + + return res; + }); + + await cashApp?.attach(`#${id}`, options); + + if (signal.aborted) { + await cashApp?.destroy(); + } + } catch (error) { + console.error('Initializing Cash App Pay failed', error); + } + }; + + start(signal); + + return () => { + abortController.abort(); + }; + }, [createPaymentRequest, options, paymentRequestOptions, payments]); + + if (callbacks) { + for (const callback of Object.keys(callbacks)) { + cashApp?.addEventListener( + callback.toLowerCase() as 'ontokenization', + (callbacks as Record) => void>)[callback] + ); + } + } + + return
; +} + +export default CashAppPay; +export * from './cash-app-pay.types'; diff --git a/src/components/cash-app-pay/cash-app-pay.types.ts b/src/components/cash-app-pay/cash-app-pay.types.ts new file mode 100644 index 00000000..ed8fbe73 --- /dev/null +++ b/src/components/cash-app-pay/cash-app-pay.types.ts @@ -0,0 +1,14 @@ +// Dependencies +import type * as Square from '@square/web-sdk'; + +export type CashAppPayProps = React.ComponentPropsWithoutRef<'div'> & { + callbacks?: { + onTokenization?(event: Square.SqEvent): void; + }; + redirectURL?: string; + referenceId?: string; + shape?: Square.CashAppPayButtonShapeValues; + size?: Square.CashAppPayButtonSizeValues; + values?: Square.CashAppPayButtonThemeValues; + width?: Square.CashAppPayButtonWidthValues; +}; diff --git a/src/components/cash-app-pay/index.ts b/src/components/cash-app-pay/index.ts new file mode 100644 index 00000000..8046686b --- /dev/null +++ b/src/components/cash-app-pay/index.ts @@ -0,0 +1,2 @@ +export { default as CashAppPay } from './cash-app-pay'; +export * from './cash-app-pay'; diff --git a/src/index.ts b/src/index.ts index 9c636d66..da3f883f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './components/ach'; export * from './components/afterpay'; export * from './components/apple-pay'; +export * from './components/cash-app-pay'; export * from './components/credit-card'; export * from './components/divider'; export * from './components/gift-card'; diff --git a/yarn.lock b/yarn.lock index 520b49d3..2e8eb58c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -697,11 +697,16 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node@*", "@types/node@^17.0.34": +"@types/node@*": version "17.0.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef" integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== +"@types/node@^17.0.35": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a" + integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg== + "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -1102,7 +1107,7 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== -array-includes@^3.1.4: +array-includes@^3.1.4, array-includes@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== @@ -1118,7 +1123,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flatmap@^1.2.5: +array.prototype.flatmap@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== @@ -1889,25 +1894,25 @@ eslint-plugin-jsx-a11y@^6.5.1: language-tags "^1.0.5" minimatch "^3.0.4" -eslint-plugin-react@^7.29.4: - version "7.29.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" - integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== +eslint-plugin-react@^7.30.0: + version "7.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.0.tgz#8e7b1b2934b8426ac067a0febade1b13bd7064e3" + integrity sha512-RgwH7hjW48BleKsYyHK5vUAvxtE9SMPDKmcPRQgtRCYaZA0XQPt5FSkrU3nhz5ifzMZcA8opwmRJ2cmOO8tr5A== dependencies: - array-includes "^3.1.4" - array.prototype.flatmap "^1.2.5" + array-includes "^3.1.5" + array.prototype.flatmap "^1.3.0" doctrine "^2.1.0" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" object.entries "^1.1.5" object.fromentries "^2.0.5" - object.hasown "^1.1.0" + object.hasown "^1.1.1" object.values "^1.1.5" prop-types "^15.8.1" resolve "^2.0.0-next.3" semver "^6.3.0" - string.prototype.matchall "^4.0.6" + string.prototype.matchall "^4.0.7" eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" @@ -3298,7 +3303,7 @@ object.fromentries@^2.0.5: define-properties "^1.1.3" es-abstract "^1.19.1" -object.hasown@^1.1.0: +object.hasown@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3" integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== @@ -3967,7 +3972,7 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.matchall@^4.0.6: +string.prototype.matchall@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==