├── .gitattributes ├── .prettierignore ├── .yarnrc.yml ├── jest.setup.ts ├── .eslintignore ├── .husky └── pre-commit ├── src ├── components │ ├── ach │ │ ├── index.ts │ │ ├── ach.utils.ts │ │ ├── ach.styles.ts │ │ ├── ach.tsx │ │ └── ach.types.ts │ ├── afterpay │ │ ├── index.ts │ │ ├── afterpay.styles.ts │ │ ├── afterpay.types.ts │ │ └── afterpay.tsx │ ├── apple-pay │ │ ├── apple-pay.types.ts │ │ ├── index.ts │ │ ├── apple-pay.styles.ts │ │ └── apple-pay.tsx │ ├── divider │ │ ├── index.ts │ │ ├── divider.styles.ts │ │ └── divider.tsx │ ├── gift-card │ │ ├── index.ts │ │ ├── gift-card.styles.ts │ │ ├── gift-card.types.ts │ │ └── gift-card.tsx │ ├── google-pay │ │ ├── index.ts │ │ ├── google-pay.types.ts │ │ ├── google-pay.styles.ts │ │ └── google-pay.tsx │ ├── cash-app-pay │ │ ├── index.ts │ │ ├── cash-app-pay.types.ts │ │ └── cash-app-pay.tsx │ ├── credit-card │ │ ├── index.ts │ │ ├── credit-card.styles.ts │ │ ├── credit-card.tsx │ │ └── credit-card.types.ts │ ├── error-screen │ │ ├── index.ts │ │ ├── error-screen.styles.ts │ │ └── error-screen.tsx │ └── payment-form │ │ ├── index.ts │ │ ├── payment-form.tsx │ │ └── payment-form.types.ts ├── contexts │ ├── form │ │ ├── index.ts │ │ ├── form.tsx │ │ └── form.types.ts │ └── afterpay │ │ ├── index.ts │ │ ├── afterpay.types.ts │ │ └── afterpay.tsx ├── stitches.config.ts ├── hooks │ ├── use-isomorphic-layout-effect.ts │ ├── use-dynamic-callback.ts │ └── use-event-listener.ts └── index.ts ├── .github ├── CODEOWNERS ├── release.yml ├── workflows │ ├── size.yml │ ├── publish.yml │ └── main.yml ├── lock.yml ├── .kodiak.toml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── PULL_REQUEST_TEMPLATE.md ├── __tests__ └── fake.test.ts ├── .vscode └── settings.json ├── .prettierrc ├── tsconfig.node.json ├── .editorconfig ├── tsconfig.build.json ├── tsconfig.json ├── LICENSE ├── .eslintrc.json ├── .all-contributorsrc ├── .size-limit ├── vite.config.ts ├── package.json ├── .gitignore ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /src/types 4 | **/*.types.ts -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint --fix 5 | -------------------------------------------------------------------------------- /src/components/ach/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Ach } from './ach'; 2 | export * from './ach'; 3 | -------------------------------------------------------------------------------- /src/contexts/form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FormProvider } from './form'; 2 | export * from './form'; 3 | -------------------------------------------------------------------------------- /src/components/afterpay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Afterpay } from './afterpay'; 2 | export * from './afterpay'; 3 | -------------------------------------------------------------------------------- /src/components/apple-pay/apple-pay.types.ts: -------------------------------------------------------------------------------- 1 | export type ApplePayProps = React.ComponentPropsWithoutRef<'div'>; 2 | -------------------------------------------------------------------------------- /src/components/divider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Divider } from './divider'; 2 | export * from './divider'; 3 | -------------------------------------------------------------------------------- /src/components/apple-pay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ApplePay } from './apple-pay'; 2 | export * from './apple-pay'; 3 | -------------------------------------------------------------------------------- /src/components/gift-card/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GiftCard } from './gift-card'; 2 | export * from './gift-card'; 3 | -------------------------------------------------------------------------------- /src/components/google-pay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GooglePay } from './google-pay'; 2 | export * from './google-pay'; 3 | -------------------------------------------------------------------------------- /src/contexts/afterpay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AfterpayProvider } from './afterpay'; 2 | export * from './afterpay'; 3 | -------------------------------------------------------------------------------- /src/components/cash-app-pay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CashAppPay } from './cash-app-pay'; 2 | export * from './cash-app-pay'; 3 | -------------------------------------------------------------------------------- /src/components/credit-card/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CreditCard } from './credit-card'; 2 | export * from './credit-card'; 3 | -------------------------------------------------------------------------------- /src/components/error-screen/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ErrorScreen } from './error-screen'; 2 | export * from './error-screen'; 3 | -------------------------------------------------------------------------------- /src/components/payment-form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PaymentForm } from './payment-form'; 2 | export * from './payment-form'; 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Learn how to add code owners here: 2 | # https://help.github.com/en/articles/about-code-owners 3 | 4 | * @danestves @joeccr -------------------------------------------------------------------------------- /__tests__/fake.test.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | describe('Fake', () => { 4 | it('should be true', () => { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/stitches.config.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import { createStitches } from '@stitches/react'; 3 | 4 | export const { keyframes, styled } = createStitches(); 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "[javascript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/ach/ach.utils.ts: -------------------------------------------------------------------------------- 1 | import { PlaidEventName } from "@square/web-sdk"; 2 | 3 | export function transformPlaidEventName(name: string) { 4 | return name.replace(/([A-Z])/g, '_$1').toUpperCase() as PlaidEventName; 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsdocPreferCodeFences": true, 3 | "jsdocPrintWidth": 80, 4 | "plugins": ["./node_modules/prettier-plugin-jsdoc"], 5 | "printWidth": 120, 6 | "semi": true, 7 | "singleQuote": true, 8 | "tsdoc": true 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true 7 | }, 8 | "include": ["package.json", "vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/use-isomorphic-layout-effect.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | 4 | const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect; 5 | 6 | export { useIsomorphicLayoutEffect }; 7 | -------------------------------------------------------------------------------- /src/components/google-pay/google-pay.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | import type * as React from 'react'; 4 | 5 | export interface GooglePayProps extends Square.GooglePayButtonOptions, React.ComponentPropsWithoutRef<'div'> {} 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: Documentation Changes 📃 4 | labels: 5 | - docs 6 | - title: Features 🎉 7 | labels: 8 | - feat 9 | - title: Bug Fixes 🐛 10 | labels: 11 | - fix 12 | - title: Other Changes 🤷‍♂️ 13 | labels: 14 | - '*' 15 | -------------------------------------------------------------------------------- /src/components/apple-pay/apple-pay.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { styled } from '~/stitches.config'; 3 | 4 | export const ApplePayContainer = styled('div', { 5 | ApplePayButtonStyle: 'black', 6 | ApplePayButtonType: 'plain', 7 | cursor: 'pointer', 8 | display: 'inline-block', 9 | height: 48, 10 | WebkitAppearance: '-apple-pay-button', 11 | width: '100%', 12 | }); 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/ach'; 2 | export * from './components/afterpay'; 3 | export * from './components/apple-pay'; 4 | export * from './components/cash-app-pay'; 5 | export * from './components/credit-card'; 6 | export * from './components/divider'; 7 | export * from './components/gift-card'; 8 | export * from './components/google-pay'; 9 | export * from './components/payment-form'; 10 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: Size limit 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | size: 11 | runs-on: ubuntu-latest 12 | env: 13 | CI_JOB_NUMBER: 1 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: andresz1/size-limit-action@v1 17 | with: 18 | github_token: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for lock-threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 365 5 | # Comment to post before locking. Set to `false` to disable 6 | lockComment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' 7 | -------------------------------------------------------------------------------- /src/components/afterpay/afterpay.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { keyframes, styled } from '~/stitches.config'; 3 | 4 | const pulse = keyframes({ 5 | '0%, 100%': { 6 | opacity: 1, 7 | }, 8 | '50%': { 9 | opacity: 0.5, 10 | }, 11 | }); 12 | 13 | export const ButtonLoader = styled('div', { 14 | animation: `${pulse()} 2s cubic-bezier(0.4, 0, 0.6, 1) infinite`, 15 | background: '#F3F4F6', 16 | borderRadius: 6, 17 | height: 40, 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/google-pay/google-pay.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { keyframes, styled } from '~/stitches.config'; 3 | 4 | const pulse = keyframes({ 5 | '0%, 100%': { 6 | opacity: 1, 7 | }, 8 | '50%': { 9 | opacity: 0.5, 10 | }, 11 | }); 12 | 13 | export const ButtonLoader = styled('div', { 14 | animation: `${pulse()} 2s cubic-bezier(0.4, 0, 0.6, 1) infinite`, 15 | background: '#F3F4F6', 16 | borderRadius: 4, 17 | height: 40, 18 | }); 19 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | version = 1 3 | 4 | [merge] 5 | automerge_label = "ready to land" 6 | require_automerge_label = false 7 | method = "squash" 8 | delete_branch_on_merge = true 9 | optimistic_updates = true 10 | prioritize_ready_to_merge = true 11 | notify_on_conflict = false 12 | 13 | [merge.message] 14 | title = "pull_request_title" 15 | body = "pull_request_body" 16 | include_pr_number = true 17 | body_type = "markdown" 18 | strip_html_comments = true -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | branches: [main] 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 14 16 | registry-url: https://registry.npmjs.org/ 17 | - run: npm install 18 | - run: npm run build 19 | - run: npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 22 | -------------------------------------------------------------------------------- /src/components/cash-app-pay/cash-app-pay.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | 4 | export type CashAppPayProps = React.ComponentPropsWithoutRef<'div'> & { 5 | callbacks?: { 6 | onTokenization?(event: Square.SqEvent): void; 7 | }; 8 | redirectURL?: string; 9 | referenceId?: string; 10 | shape?: Square.CashAppPayButtonShapeValues; 11 | size?: Square.CashAppPayButtonSizeValues; 12 | values?: Square.CashAppPayButtonThemeValues; 13 | width?: Square.CashAppPayButtonWidthValues; 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/use-dynamic-callback.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | 4 | // Internals 5 | import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'; 6 | 7 | export function useDynamicCallback(callback: T): T; 8 | export function useDynamicCallback(callback: (...args: Record[]) => void) { 9 | const ref = React.useRef(callback); 10 | 11 | useIsomorphicLayoutEffect(() => { 12 | ref.current = callback; 13 | }, [callback]); 14 | 15 | return React.useCallback((...args: Record[]) => ref.current(...args), []); 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Feature Requests & Questions 4 | url: https://github.com/weareseeed/react-square-web-payments-sdk/discussions 5 | about: Please ask and answer questions here. 6 | - name: 📄 Web Payments SDK Overview 7 | url: https://developer.squareup.com/docs/web-payments/overview 8 | about: Learn more about the Web Payments SDK with examples and tutorials. 9 | - name: 📄 Web Paymnents SDK Reference 10 | url: https://developer.squareup.com/reference/sdks/web/payments 11 | about: Learn with the API Reference for the Web Payments SDK. 12 | -------------------------------------------------------------------------------- /src/components/divider/divider.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { styled } from '~/stitches.config'; 3 | 4 | export const Line = styled('div', { 5 | background: 'rgba(0, 0, 0, 0.1)', 6 | color: 'rgba(0, 0, 0, 0.55)', 7 | fontSize: 12, 8 | height: 1, 9 | lineHeight: '20px', 10 | margin: '30px 0', 11 | position: 'relative', 12 | textTransform: 'uppercase', 13 | width: '100%', 14 | }); 15 | 16 | export const SpanText = styled('span', { 17 | background: '#FAFAFA', 18 | fontFamily: 'sans-serif, system-ui', 19 | left: 'calc(50% - 1em - 5px)', 20 | padding: '0 10px', 21 | position: 'absolute', 22 | top: -10, 23 | }); 24 | -------------------------------------------------------------------------------- /src/components/divider/divider.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Stitches from '@stitches/react'; 4 | 5 | // Internals 6 | import { Line, SpanText } from './divider.styles'; 7 | 8 | interface DividerProps extends Stitches.ComponentProps { 9 | spanProps?: Omit, 'children'>; 10 | } 11 | 12 | const Divider = ({ children, spanProps, ...props }: DividerProps): React.ReactElement => ( 13 | 14 | {children ?? 'or'} 15 | 16 | ); 17 | 18 | export default Divider; 19 | export type { DividerProps }; 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Your checklist for this pull request 2 | 3 | 🚨Please review the [guidelines for contributing](../CONTRIBUTING.md) to this repository. 4 | 5 | - [ ] Make sure you are requesting to **pull a topic/feature/bugfix branch** (right side). Don't request your master! 6 | - [ ] Make sure you are making a pull request against the **main branch** (left side). Also you should start _your branch_ off _our main_. 7 | - [ ] Check the commit's or even all commits' message styles matches our requested structure. 8 | - [ ] Check your code additions will fail neither code linting checks nor unit test. 9 | 10 | ### Description 11 | 12 | Please describe your pull request and add all the information and images that you need. 13 | 14 | 🧡 Thank you! 15 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react", 17 | "outDir": "dist", 18 | "declaration": true, 19 | "declarationMap": true, 20 | "baseUrl": "./", 21 | "paths": { 22 | "~/*": ["src/*"] 23 | } 24 | }, 25 | "include": ["src"], 26 | "exclude": ["node_modules"], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react", 17 | "outDir": "dist", 18 | "declaration": true, 19 | "declarationMap": true, 20 | "baseUrl": "./", 21 | "paths": { 22 | "~/*": ["src/*"] 23 | } 24 | }, 25 | "globals": { 26 | "JSX": true 27 | }, 28 | "include": ["src", "__tests__"], 29 | "exclude": ["node_modules"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ach/ach.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { styled } from '~/stitches.config'; 3 | 4 | export const PayButton = styled('button', { 5 | backgroundColor: '#006aff', 6 | borderRadius: 5, 7 | boxShadow: 1, 8 | color: '#fff', 9 | cursor: 'pointer', 10 | borderStyle: 'none', 11 | fontSize: 16, 12 | fontWeight: 500, 13 | lineHeight: '24px', 14 | outline: 'none', 15 | padding: 12, 16 | userSelect: 'none', 17 | width: '100%', 18 | '&:active': { 19 | backgroundColor: 'rgb(0, 85, 204)', 20 | }, 21 | '&:disabled': { 22 | backgroundColor: 'rgba(0, 0, 0, 0.05)', 23 | color: 'rgba(0, 0, 0, 0.3)', 24 | cursor: 'not-allowed', 25 | }, 26 | span: { 27 | lineHeight: 1, 28 | verticalAlign: 'middle', 29 | }, 30 | }); 31 | 32 | export const SvgIcon = styled('svg', { 33 | display: 'inline-flex', 34 | height: 24, 35 | marginRight: 14, 36 | verticalAlign: 'middle', 37 | width: 36, 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/gift-card/gift-card.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { keyframes, styled } from '~/stitches.config'; 3 | 4 | const pulse = keyframes({ 5 | '0%, 100%': { 6 | opacity: 1, 7 | }, 8 | '50%': { 9 | opacity: 0.5, 10 | }, 11 | }); 12 | 13 | export const LoadingCard = styled('div', { 14 | animation: `${pulse()} 2s cubic-bezier(0.4, 0, 0.6, 1) infinite`, 15 | background: '#F3F4F6', 16 | borderRadius: 6, 17 | height: 50, 18 | marginBottom: 39, 19 | position: 'relative', 20 | }); 21 | 22 | export const PayButton = styled('button', { 23 | backgroundColor: '#006aff', 24 | borderRadius: 5, 25 | boxShadow: 1, 26 | color: '#fff', 27 | cursor: 'pointer', 28 | borderStyle: 'none', 29 | fontSize: 16, 30 | fontWeight: 500, 31 | lineHeight: '24px', 32 | outline: 'none', 33 | padding: 12, 34 | userSelect: 'none', 35 | width: '100%', 36 | '&:active': { 37 | backgroundColor: 'rgb(0, 85, 204)', 38 | }, 39 | '&:disabled': { 40 | backgroundColor: 'rgba(0, 0, 0, 0.05)', 41 | color: 'rgba(0, 0, 0, 0.3)', 42 | cursor: 'not-allowed', 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/components/credit-card/credit-card.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { keyframes, styled } from '~/stitches.config'; 3 | 4 | const pulse = keyframes({ 5 | '0%, 100%': { 6 | opacity: 1, 7 | }, 8 | '50%': { 9 | opacity: 0.5, 10 | }, 11 | }); 12 | 13 | export const LoadingCard = styled('div', { 14 | animation: `${pulse()} 2s cubic-bezier(0.4, 0, 0.6, 1) infinite`, 15 | background: '#F3F4F6', 16 | borderRadius: 6, 17 | height: 50, 18 | marginBottom: 39, 19 | position: 'relative', 20 | }); 21 | 22 | export const PayButton = styled('button', { 23 | backgroundColor: '#006aff', 24 | borderRadius: 5, 25 | boxShadow: 1, 26 | color: '#fff', 27 | cursor: 'pointer', 28 | borderStyle: 'none', 29 | fontSize: 16, 30 | fontWeight: 500, 31 | lineHeight: '24px', 32 | outline: 'none', 33 | padding: 12, 34 | userSelect: 'none', 35 | width: '100%', 36 | '&:active': { 37 | backgroundColor: 'rgb(0, 85, 204)', 38 | }, 39 | '&:disabled': { 40 | backgroundColor: 'rgba(0, 0, 0, 0.05)', 41 | color: 'rgba(0, 0, 0, 0.3)', 42 | cursor: 'not-allowed', 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/components/afterpay/afterpay.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | 4 | export interface AfterpayButtonProps 5 | extends Omit, 6 | React.ComponentPropsWithoutRef<'div'> { 7 | Button?: React.ElementType; 8 | } 9 | 10 | export interface AfterpayMessageBaseProps extends React.ComponentPropsWithoutRef<'div'> { 11 | badgeTheme?: Square.AfterpayBadgeThemeValues; 12 | id?: string; 13 | modalLinkStyle?: Square.AfterpayModalLinkStyleValues; 14 | modalTheme?: Square.AfterpayModalThemeValues; 15 | size?: Square.AfterpaySizeValues; 16 | } 17 | export interface AfterpayMessageCustomComponentProps { 18 | component?: { 19 | Message?: React.ElementType; 20 | }; 21 | modalTheme?: Square.AfterpayModalThemeValues; 22 | } 23 | export interface AfterpayMessageProps extends AfterpayMessageBaseProps { 24 | component?: { 25 | Message?: React.ElementType; 26 | }; 27 | } 28 | 29 | export interface AfterpayWidgetProps 30 | extends Square.AfterpayCheckoutWidgetOptions, 31 | React.ComponentPropsWithoutRef<'div'> {} 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Seeed LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/payment-form/payment-form.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | 4 | // Internals 5 | import { FormProvider } from '~/contexts/form'; 6 | import type { PaymentFormProps } from './payment-form.types'; 7 | 8 | function RenderPaymentForm( 9 | { 10 | applicationId, 11 | cardTokenizeResponseReceived, 12 | locationId, 13 | children, 14 | formProps = { 15 | 'aria-label': 'Payment form', 16 | id: 'rswps-form', 17 | }, 18 | overrides, 19 | ...props 20 | }: PaymentFormProps, 21 | ref: React.LegacyRef 22 | ) { 23 | return ( 24 | 31 |
32 | {children} 33 |
34 |
35 | ); 36 | } 37 | 38 | const PaymentForm = React.forwardRef(RenderPaymentForm); 39 | 40 | export default PaymentForm; 41 | export * from './payment-form.types'; 42 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jest": true, 5 | "node": true 6 | }, 7 | "globals": { 8 | "JSX": true 9 | }, 10 | "extends": ["plugin:@typescript-eslint/recommended", "plugin:react/recommended", "prettier"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": "latest", 17 | "sourceType": "module" 18 | }, 19 | "plugins": ["react", "jsx-a11y", "@typescript-eslint"], 20 | "rules": { 21 | "@typescript-eslint/ban-ts-comment": [ 22 | 2, 23 | { 24 | "ts-ignore": "allow-with-description" 25 | } 26 | ], 27 | "jsx-a11y/alt-text": "warn", 28 | "jsx-a11y/aria-props": "warn", 29 | "jsx-a11y/aria-proptypes": "warn", 30 | "jsx-a11y/aria-unsupported-elements": "warn", 31 | "jsx-a11y/role-has-required-aria-props": "warn", 32 | "jsx-a11y/role-supports-aria-props": "warn", 33 | "react/react-in-jsx-scope": "off", 34 | "react/jsx-sort-props": 2, 35 | "react/prop-types": "off" 36 | }, 37 | "settings": { 38 | "react": { 39 | "version": "detect" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/use-event-listener.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | 4 | export const getRefElement = ( 5 | element?: React.RefObject | T 6 | ): Element | T | undefined | null | React.RefObject => { 7 | if (element && 'current' in element) { 8 | return element.current; 9 | } 10 | 11 | return element; 12 | }; 13 | 14 | const isSsr = !(typeof window !== 'undefined' && window.document?.createElement); 15 | 16 | type UseEventListenerProps = { 17 | type: keyof WindowEventMap; 18 | listener: EventListener; 19 | element?: React.RefObject | HTMLElement | Document | Window | null; 20 | options?: AddEventListenerOptions; 21 | }; 22 | 23 | function useEventListener({ type, listener, element = isSsr ? undefined : window, options }: UseEventListenerProps) { 24 | const savedListener = React.useRef(); 25 | 26 | React.useEffect(() => { 27 | savedListener.current = listener; 28 | }, [listener]); 29 | 30 | const handleEventListener = React.useCallback((event: Event) => { 31 | savedListener.current?.(event); 32 | }, []); 33 | 34 | React.useEffect(() => { 35 | const target = getRefElement(element) as unknown as Element; 36 | 37 | target?.addEventListener(type, handleEventListener, options); 38 | 39 | return () => target?.removeEventListener(type, handleEventListener); 40 | }, [type, element, options, handleEventListener]); 41 | } 42 | 43 | export { useEventListener }; 44 | export type { UseEventListenerProps }; 45 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "danestves", 10 | "name": "Daniel Esteves", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/31737273?v=4", 12 | "profile": "https://danestves.com/", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "example", 17 | "test" 18 | ] 19 | }, 20 | { 21 | "login": "rsaer", 22 | "name": "Rowland Saer", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/38730951?v=4", 24 | "profile": "https://github.com/rsaer", 25 | "contributions": [ 26 | "doc" 27 | ] 28 | }, 29 | { 30 | "login": "WinglessFrame", 31 | "name": "Hleb Siamionau", 32 | "avatar_url": "https://avatars.githubusercontent.com/u/68775653?v=4", 33 | "profile": "https://github.com/WinglessFrame", 34 | "contributions": [ 35 | "code" 36 | ] 37 | }, 38 | { 39 | "login": "gabrielelpidio", 40 | "name": "Gabriel De Andrade", 41 | "avatar_url": "https://avatars.githubusercontent.com/u/30420087?v=4", 42 | "profile": "https://github.com/gabrielelpidio", 43 | "contributions": [ 44 | "code", 45 | "doc", 46 | "example" 47 | ] 48 | } 49 | ], 50 | "contributorsPerLine": 7, 51 | "projectName": "react-square-web-payments-sdk", 52 | "projectOwner": "weareseeed", 53 | "repoType": "github", 54 | "repoHost": "https://github.com", 55 | "skipCi": true 56 | } 57 | -------------------------------------------------------------------------------- /src/components/payment-form/payment-form.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | export type PaymentFormProps = { 6 | /** 7 | * Identifies the calling form with a verified application ID generated from 8 | * the Square Application Dashboard. 9 | */ 10 | applicationId: string; 11 | /** 12 | * Invoked when payment form receives the result of a tokenize generation 13 | * request. The result will be a valid credit card or wallet token, or an error. 14 | */ 15 | cardTokenizeResponseReceived: ( 16 | props: Square.TokenResult, 17 | verifiedBuyer?: Square.VerifyBuyerResponseDetails | null 18 | ) => void; 19 | children: React.ReactNode; 20 | /** 21 | * Identifies the location of the merchant that is taking the payment. 22 | * Obtained from the Square Application Dashboard - Locations tab. 23 | */ 24 | locationId: string; 25 | /** 26 | * **Required for digital wallets** 27 | * 28 | * Encapsulates the details of an Apple Pay, Google Pay, or Afterpay/Clearpay 29 | * request for payment and provides a means of listening for shipping option 30 | * and shipping contact changes via event listeners. 31 | */ 32 | createPaymentRequest?: () => Square.PaymentRequestOptions; 33 | /** 34 | * **Strong Customer Authentication** 35 | * 36 | * The verification details parameter, passed to the `payments.verifyBuyer()` 37 | * function, for cases in which he buyer is being charged or the card is being 38 | * stored on file. 39 | */ 40 | createVerificationDetails?: () => Square.ChargeVerifyBuyerDetails | Square.StoreVerifyBuyerDetails; 41 | formProps?: Omit, 'role'>; 42 | /** Override the default payment form configuration. */ 43 | overrides?: { 44 | /** The URL of the Square payment form script. */ 45 | scriptSrc?: string; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/error-screen/error-screen.styles.ts: -------------------------------------------------------------------------------- 1 | // Internals 2 | import { styled } from '~/stitches.config'; 3 | 4 | export const Container = styled('div', { 5 | background: '#FFFFFF', 6 | border: '1px solid #e0e2e4', 7 | borderLeft: '4px solid', 8 | borderLeftColor: '#d92b2b', 9 | borderRadius: 4, 10 | boxShadow: '0 0 2px rgb(0 0 0 / 10%), 0 2px 2px rgb(0 0 0 / 10%), 0 1px 2px rgb(0 0 0 / 10%)', 11 | display: 'flex', 12 | flexFlow: 'row nowrap', 13 | margin: '24px auto', 14 | maxWidth: 800, 15 | padding: '16px 24px 16px 16px', 16 | width: '100%', 17 | }); 18 | 19 | export const SvgContainer = styled('div', { 20 | alignItems: 'center', 21 | display: 'inline-flex', 22 | marginRight: 16, 23 | maxHeight: 24, 24 | }); 25 | 26 | export const Svg = styled('svg', { 27 | display: 'inline-block', 28 | verticalAlign: 'middle', 29 | }); 30 | 31 | export const TextContainer = styled('div', { 32 | flexGrow: 1, 33 | fontSize: 16, 34 | lineHeight: 28, 35 | }); 36 | 37 | export const Title = styled('h4', { 38 | color: '#373f4a', 39 | fontFamily: 40 | 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";', 41 | fontSize: 14, 42 | fontWeight: 500, 43 | marginBottom: 0, 44 | marginTop: 0, 45 | letterSpacing: 0, 46 | lineHeight: '24px', 47 | textTransform: 'none', 48 | }); 49 | 50 | export const Text = styled('p', { 51 | color: '#373f4a', 52 | fontFamily: 53 | 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";', 54 | fontSize: 14, 55 | lineHeight: '24px', 56 | marginBottom: 0, 57 | marginTop: 0, 58 | maxWidth: 700, 59 | width: '100%', 60 | '& a': { 61 | color: '#006be6', 62 | textDecoration: 'none', 63 | '&:hover': { 64 | textDecoration: 'underline', 65 | }, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /.size-limit: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "`{ Ach, PaymentForm }`", 4 | "path": "dist/index.es.mjs", 5 | "import": "{ Ach, PaymentForm }" 6 | }, 7 | { 8 | "name": "`{ Afterpay, AfterpayProvider, PaymentForm }`", 9 | "path": "dist/index.es.mjs", 10 | "import": "{ Afterpay, AfterpayProvider, PaymentForm }" 11 | }, 12 | { 13 | "name": "`{ Afterpay, AfterpayMessage, AfterpayProvider, PaymentForm }`", 14 | "path": "dist/index.es.mjs", 15 | "import": "{ Afterpay, AfterpayMessage, AfterpayProvider, PaymentForm }" 16 | }, 17 | { 18 | "name": "`{ Afterpay, AfterpayWidget, AfterpayProvider, PaymentForm }`", 19 | "path": "dist/index.es.mjs", 20 | "import": "{ Afterpay, AfterpayWidget, AfterpayProvider, PaymentForm }" 21 | }, 22 | { 23 | "name": "`{ ApplePay, PaymentForm }`", 24 | "path": "dist/index.es.mjs", 25 | "import": "{ ApplePay, PaymentForm }" 26 | }, 27 | { 28 | "name": "`{ CashAppPay, PaymentForm }`", 29 | "path": "dist/index.es.mjs", 30 | "import": "{ CashAppPay, PaymentForm }" 31 | }, 32 | { 33 | "name": "`{ CreditCard, PaymentForm }`", 34 | "path": "dist/index.es.mjs", 35 | "import": "{ CreditCard, PaymentForm }" 36 | }, 37 | { 38 | "name": "`{ GiftCard, PaymentForm }`", 39 | "path": "dist/index.es.mjs", 40 | "import": "{ GiftCard, PaymentForm }" 41 | }, 42 | { 43 | "name": "`{ GooglePay, PaymentForm }`", 44 | "path": "dist/index.es.mjs", 45 | "import": "{ GooglePay, PaymentForm }" 46 | }, 47 | { 48 | "name": "`{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }`", 49 | "path": "dist/index.es.mjs", 50 | "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }" 51 | }, 52 | { 53 | "name": "Format: cjs, `{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }`", 54 | "path": "dist/index.cjs.js", 55 | "import": "{ Ach, Afterpay, AfterpayMessage, AfterpayWidget, ApplePay, CashAppPay, GiftCard, GooglePay, PaymentForm }" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /src/components/error-screen/error-screen.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | 4 | // Internals 5 | import { Container, Svg, SvgContainer, Text, TextContainer, Title } from './error-screen.styles'; 6 | 7 | type ErrorScreenProps = { 8 | isDevelopment?: boolean; 9 | }; 10 | 11 | function RenderErrorScreen( 12 | { isDevelopment = process.env.NODE_ENV === 'development' }: ErrorScreenProps, 13 | ref: React.LegacyRef 14 | ) { 15 | if (process.env.NODE_ENV !== 'development') { 16 | throw new Error('Please contact your developer to provide the required parameters to use the Web Payments SDK.'); 17 | } 18 | 19 | return ( 20 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | {isDevelopment ? 'No location ID or app ID found. Please check your configuration.' : 'Error'} 36 | 37 | 38 | {isDevelopment ? ( 39 | <> 40 | Please provide a location ID or app ID to use the{' '} 41 | 46 | Web Payments SDK 47 | {' '} 48 | to take payments on a web client. 49 | 50 | ) : ( 51 | <>An error occurred has ocurred while loading your Payment Form. 52 | )} 53 | 54 | 55 | 56 | ); 57 | } 58 | 59 | const ErrorScreen = React.forwardRef(RenderErrorScreen); 60 | 61 | export default ErrorScreen; 62 | export type { ErrorScreenProps }; 63 | -------------------------------------------------------------------------------- /src/contexts/afterpay/afterpay.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | import type * as React from 'react'; 4 | 5 | export type AfterpayProviderProps = { 6 | children: React.ReactNode; 7 | /** 8 | * Occurs when a buyer chooses a shipping address in Afterpay/Clearpay. 9 | * 10 | * It is required for you to subscribe to this event if shipping if marked a required. 11 | * 12 | * @example 13 | * 14 | * ```js 15 | * (contact) => { 16 | * return { 17 | * shippingOptions: [ 18 | * { 19 | * id: 'shippingOption1', 20 | * label: 'Free Shipping', 21 | * amount: '0.00', 22 | * total: '27.50', // Line Items + Discounts + Taxes + Shipping 23 | * taxLineItems: [ 24 | * { 25 | * id: 'taxItem1', 26 | * label: 'Taxes', 27 | * amount: '2.50', 28 | * }, 29 | * ], 30 | * }, 31 | * { 32 | * id: 'shippingOption2', 33 | * label: 'Express Shipping', 34 | * amount: '10.00', 35 | * total: '38.50', // Line Items + Discounts + Taxes + Shipping 36 | * taxLineItems: [ 37 | * { 38 | * id: 'taxItem1', 39 | * label: 'Taxes', 40 | * amount: '3.50', 41 | * }, 42 | * ], 43 | * }, 44 | * ], 45 | * }; 46 | * }; 47 | * ``` 48 | */ 49 | onShippingAddressChange?: Square.AfterpayShippingOptionCallback; 50 | /** 51 | * Occurs when a buyer chooses a shipping option in Afterpay/Clearpay. 52 | * 53 | * Subscribe to this event if you want to be alerted of shipping options 54 | * changes. This event if informational only, and does not update the payment request. 55 | * 56 | * @example 57 | * 58 | * ```js 59 | * req.addEventListener( 60 | * 'afterpay_shippingaoptionchanged', 61 | * function (option) { 62 | * // used for informational purposes only 63 | * } 64 | * ); 65 | * ``` 66 | */ 67 | onShippingOptionChange?: Square.AfterpayShippingOptionCallback; 68 | }; 69 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Dependencies 4 | import react from '@vitejs/plugin-react'; 5 | import { resolve } from 'path'; 6 | import { defineConfig } from 'vite'; 7 | import tsconfigPaths from 'vite-tsconfig-paths'; 8 | 9 | // Internals 10 | import { peerDependencies, dependencies } from './package.json'; 11 | 12 | const externalPackages = [ 13 | // We need to build @square/web-sdk with the package to avoid problems with ES Modules in bundlers like Next.js 14 | ...Object.keys(dependencies).filter((name) => name !== '@square/web-sdk'), 15 | ...Object.keys(peerDependencies), 16 | ]; 17 | 18 | // Creating regexes of the packages to make sure subpaths of the 19 | // packages are also treated as external 20 | const regexesOfPackages = externalPackages.map((packageName) => new RegExp(`^${packageName}(/.*)?`)); 21 | 22 | export default defineConfig({ 23 | build: { 24 | lib: { 25 | entry: resolve(__dirname, 'src', 'index.ts'), 26 | formats: ['cjs', 'es'], 27 | fileName: (ext) => `rswps.${ext}.js`, 28 | }, 29 | rollupOptions: { 30 | external: regexesOfPackages, 31 | output: [ 32 | { 33 | dir: resolve(__dirname, 'dist'), 34 | entryFileNames: '[name].cjs.js', 35 | exports: 'named', 36 | format: 'cjs', 37 | preserveModules: true, 38 | preserveModulesRoot: 'src', 39 | }, 40 | { 41 | dir: resolve(__dirname, 'dist'), 42 | entryFileNames: '[name].es.mjs', 43 | exports: 'named', 44 | format: 'es', 45 | preserveModules: true, 46 | preserveModulesRoot: 'src', 47 | }, 48 | ], 49 | }, 50 | target: 'esnext', 51 | sourcemap: true, 52 | }, 53 | plugins: [ 54 | react({ 55 | jsxRuntime: 'classic', 56 | }), 57 | tsconfigPaths(), 58 | ], 59 | resolve: { 60 | alias: { 61 | '~': resolve(__dirname, 'src'), 62 | }, 63 | }, 64 | test: { 65 | coverage: { 66 | reporter: ['text', 'json', 'html'], 67 | }, 68 | environment: 'jsdom', 69 | globals: true, 70 | setupFiles: './jest.setup.ts', 71 | }, 72 | }); 73 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | build: 11 | name: 🏗 Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 14 19 | cache: 'yarn' 20 | cache-dependency-path: yarn.lock 21 | 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 25 26 | 27 | - name: tune linux network 28 | run: sudo ethtool -K eth0 tx off rx off 29 | 30 | - name: Install dependencies 31 | run: yarn install --frozen-lockfile --check-files 32 | 33 | - name: Cache build 34 | uses: actions/cache@v3 35 | id: cache-build 36 | with: 37 | path: ./* 38 | key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} 39 | 40 | lint: 41 | name: ⬣ ESLint 42 | runs-on: ubuntu-latest 43 | needs: [build] 44 | steps: 45 | - name: Setup node 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: 14 49 | 50 | - name: Restore build 51 | uses: actions/cache@v3 52 | id: restore-build 53 | with: 54 | path: ./* 55 | key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} 56 | 57 | - name: Lint 58 | run: yarn lint 59 | 60 | test: 61 | name: 🧪 Test 62 | runs-on: ubuntu-latest 63 | needs: [build] 64 | strategy: 65 | matrix: 66 | node: ['14.x', '16.x'] 67 | os: [ubuntu-latest, windows-latest, macOS-latest] 68 | steps: 69 | - name: Setup node ${{ matrix.node }} 70 | uses: actions/setup-node@v3 71 | with: 72 | node-version: ${{ matrix.node }} 73 | 74 | # https://github.com/actions/virtual-environments/issues/1187 75 | - name: tune linux network 76 | run: sudo ethtool -K eth0 tx off rx off 77 | 78 | - name: Restore build 79 | uses: actions/cache@v3 80 | id: restore-build 81 | with: 82 | path: ./* 83 | key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} 84 | 85 | - name: Run test 86 | run: yarn test 87 | -------------------------------------------------------------------------------- /src/contexts/afterpay/afterpay.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useForm } from '../form'; 7 | import type { AfterpayProviderProps } from './afterpay.types'; 8 | 9 | export const AfterpayContext = React.createContext(null); 10 | 11 | function AfterpayProvider({ children, onShippingAddressChange, onShippingOptionChange }: AfterpayProviderProps) { 12 | const [afterpay, setAfterpay] = React.useState(null); 13 | const { createPaymentRequest, payments } = useForm(); 14 | 15 | if (!createPaymentRequest) { 16 | throw new Error('`createPaymentRequest()` is required when using digital wallets'); 17 | } 18 | 19 | React.useEffect(() => { 20 | const abortController = new AbortController(); 21 | const { signal } = abortController; 22 | 23 | const start = async (signal: AbortSignal) => { 24 | const paymentRequest = payments?.paymentRequest(createPaymentRequest); 25 | 26 | if (!paymentRequest) { 27 | throw new Error('`paymentRequest` is required when using digital wallets'); 28 | } 29 | 30 | if (onShippingAddressChange) { 31 | paymentRequest.addEventListener('afterpay_shippingaddresschanged', onShippingAddressChange); 32 | } 33 | if (onShippingOptionChange) { 34 | paymentRequest.addEventListener('afterpay_shippingoptionchanged', onShippingOptionChange); 35 | } 36 | 37 | const afterpay = await payments?.afterpayClearpay(paymentRequest).then((res) => { 38 | if (!signal.aborted) { 39 | setAfterpay(res); 40 | 41 | return res; 42 | } 43 | 44 | return null; 45 | }); 46 | 47 | if (signal.aborted) { 48 | await afterpay?.destroy(); 49 | } 50 | }; 51 | 52 | start(signal); 53 | 54 | return () => { 55 | abortController.abort(); 56 | }; 57 | }, [createPaymentRequest, payments]); 58 | 59 | return {children}; 60 | } 61 | 62 | export function useAfterpay() { 63 | const context = React.useContext(AfterpayContext); 64 | 65 | if (context === undefined) { 66 | throw new Error('`useAfterpay()` must be used within an ``'); 67 | } 68 | 69 | return context; 70 | } 71 | 72 | export default AfterpayProvider; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-square-web-payments-sdk", 3 | "version": "3.2.4-beta.1", 4 | "homepage": "https://weareseeed.github.io/react-square-web-payments-sdk/", 5 | "bugs": { 6 | "url": "https://github.com/weareseeed/react-square-web-payments-sdk/issues" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git@github.com:weareseeed/react-square-web-payments-sdk.git" 11 | }, 12 | "license": "MIT", 13 | "author": "Seeed LLC. (https://seeed.us)", 14 | "contributors": [ 15 | { 16 | "name": "Daniel Esteves", 17 | "email": "me+github@danestves.com", 18 | "url": "https://danestves.com" 19 | }, 20 | { 21 | "name": "Gabriel De Andrade", 22 | "email": "gabrieldeandradeleal@gmail.com" 23 | } 24 | ], 25 | "sideEffects": false, 26 | "exports": { 27 | ".": { 28 | "types": "./dist/index.d.ts", 29 | "require": "./dist/index.cjs.js", 30 | "import": "./dist/index.es.mjs" 31 | } 32 | }, 33 | "main": "dist/index.cjs.js", 34 | "module": "dist/index.es.mjs", 35 | "types": "dist/index.d.ts", 36 | "directories": { 37 | "src": "src" 38 | }, 39 | "files": [ 40 | "LICENSE.md", 41 | "README.md", 42 | "dist", 43 | "package.json" 44 | ], 45 | "scripts": { 46 | "prebuild": "yarn clean", 47 | "build": "run-p build:*", 48 | "build:scripts": "vite build", 49 | "build:types": "tsc --emitDeclarationOnly -p ./tsconfig.build.json && tsc-alias", 50 | "clean": "rimraf dist", 51 | "coverage": "vitest run --coverage", 52 | "dev": "vite", 53 | "format": "prettier --write 'src/**/*.{ts,tsx}'", 54 | "lint": "eslint 'src/**/*.{ts,tsx}'", 55 | "preview": "vite preview", 56 | "prepublishOnly": "yarn build", 57 | "test": "vitest run", 58 | "size": "size-limit", 59 | "size:why": "size-limit --why" 60 | }, 61 | "dependencies": { 62 | "@square/web-sdk": "^2.1.0", 63 | "@stitches/react": "^1.2.8" 64 | }, 65 | "devDependencies": { 66 | "@size-limit/preset-big-lib": "^8.0.0", 67 | "@size-limit/webpack-why": "^8.0.0", 68 | "@testing-library/jest-dom": "^5.16.4", 69 | "@testing-library/react": "^13.3.0", 70 | "@types/node": "^18.6.3", 71 | "@types/react": "^18.0.15", 72 | "@typescript-eslint/eslint-plugin": "^5.31.0", 73 | "@typescript-eslint/parser": "^5.31.0", 74 | "@vitejs/plugin-react": "^2.0.0", 75 | "c8": "^7.12.0", 76 | "eslint": "^8.20.0", 77 | "eslint-config-prettier": "^8.5.0", 78 | "eslint-plugin-jsx-a11y": "^6.6.1", 79 | "eslint-plugin-react": "^7.30.1", 80 | "jsdom": "^20.0.0", 81 | "npm-run-all": "^4.1.5", 82 | "prettier": "^2.7.1", 83 | "prettier-plugin-jsdoc": "^0.3.38", 84 | "react": "^18.2.0", 85 | "react-dom": "^18.2.0", 86 | "rimraf": "^3.0.2", 87 | "size-limit": "^8.0.0", 88 | "tsc-alias": "^1.7.0", 89 | "typescript": "^4.7.4", 90 | "vite": "^3.0.4", 91 | "vite-tsconfig-paths": "^3.5.0", 92 | "vitest": "^0.20.2" 93 | }, 94 | "peerDependencies": { 95 | "react": "^16 || ^17 || ^18" 96 | }, 97 | "publishConfig": { 98 | "access": "public" 99 | } 100 | } -------------------------------------------------------------------------------- /src/components/cash-app-pay/cash-app-pay.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useForm } from '~/contexts/form'; 7 | import type { CashAppPayProps } from './cash-app-pay.types'; 8 | 9 | function CashAppPay({ 10 | callbacks, 11 | id = 'rswps-cash-app-pay', 12 | redirectURL, 13 | referenceId, 14 | shape = 'round', 15 | size = 'medium', 16 | values = 'dark', 17 | width = 'static', 18 | ...props 19 | }: CashAppPayProps) { 20 | const [cashApp, setCashApp] = React.useState(); 21 | const { createPaymentRequest, payments } = useForm(); 22 | 23 | const paymentRequestOptions: Square.CashAppPaymentRequestOptions = React.useMemo( 24 | () => ({ 25 | redirectURL: redirectURL || window.location.href, 26 | referenceId, 27 | }), 28 | [redirectURL, referenceId] 29 | ); 30 | 31 | const options: Square.CashAppPayButtonOptions = React.useMemo(() => { 32 | const baseOptions = { 33 | shape, 34 | size, 35 | values, 36 | width, 37 | }; 38 | 39 | // if a value from options is undefined delete it from the options object 40 | return Object.keys(baseOptions).reduce((acc: Record, key) => { 41 | if (baseOptions[key as keyof typeof baseOptions] !== undefined) { 42 | acc[key as string] = baseOptions[key as keyof typeof baseOptions]; 43 | } 44 | 45 | return acc; 46 | }, {}); 47 | }, [shape, size, values, width]); 48 | 49 | React.useEffect(() => { 50 | if (!createPaymentRequest) { 51 | throw new Error('`createPaymentRequest()` is required when using digital wallets'); 52 | } 53 | 54 | const abortController = new AbortController(); 55 | const { signal } = abortController; 56 | let cashApp: Square.CashAppPay | undefined; 57 | 58 | const start = async (signal: AbortSignal) => { 59 | const paymentRequest = payments?.paymentRequest(createPaymentRequest); 60 | 61 | if (!paymentRequest) { 62 | throw new Error('`paymentRequest` is required when using digital wallets'); 63 | } 64 | 65 | try { 66 | cashApp = await payments?.cashAppPay(paymentRequest, paymentRequestOptions).then((res) => { 67 | if (signal?.aborted) { 68 | return; 69 | } 70 | 71 | setCashApp(res); 72 | 73 | return res; 74 | }); 75 | 76 | await cashApp?.attach(`#${id}`, options); 77 | } catch (error) { 78 | console.error('Initializing Cash App Pay failed', error); 79 | } 80 | }; 81 | 82 | start(signal); 83 | 84 | return () => { 85 | abortController.abort(); 86 | cashApp?.destroy(); 87 | }; 88 | }, [createPaymentRequest, options, paymentRequestOptions, payments]); 89 | 90 | if (callbacks) { 91 | for (const callback of Object.keys(callbacks)) { 92 | cashApp?.addEventListener( 93 | callback.toLowerCase() as 'ontokenization', 94 | (callbacks as Record) => void>)[callback] 95 | ); 96 | } 97 | } 98 | 99 | return
; 100 | } 101 | 102 | export default CashAppPay; 103 | export * from './cash-app-pay.types'; 104 | -------------------------------------------------------------------------------- /src/contexts/form/form.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import { payments } from '@square/web-sdk'; 4 | import type * as Square from '@square/web-sdk'; 5 | 6 | // Internals 7 | import { ErrorScreen } from '~/components/error-screen'; 8 | import { useDynamicCallback } from '~/hooks/use-dynamic-callback'; 9 | import type { FormContextType, FormProviderProps } from './form.types'; 10 | 11 | /** 12 | * Internal helper that the `PaymentForm` uses to manage internal state and 13 | * expose access to the Web Payment SDK library. 14 | */ 15 | const FormContext = React.createContext({ 16 | cardTokenizeResponseReceived: null as unknown as () => Promise, 17 | createPaymentRequest: null as unknown as Square.PaymentRequestOptions, 18 | payments: null as unknown as Square.Payments, 19 | }); 20 | 21 | function FormProvider({ applicationId, locationId, children, overrides, ...props }: FormProviderProps) { 22 | const [instance, setInstance] = React.useState(); 23 | const [createPaymentRequest] = React.useState(() => 24 | props.createPaymentRequest?.() 25 | ); 26 | 27 | React.useEffect(() => { 28 | const abortController = new AbortController(); 29 | const { signal } = abortController; 30 | 31 | async function loadPayment(signal?: AbortSignal): Promise { 32 | await payments(applicationId, locationId, overrides).then((res) => { 33 | if (res === null) { 34 | throw new Error('Square Web Payments SDK failed to load'); 35 | } 36 | 37 | if (signal?.aborted) { 38 | return; 39 | } 40 | 41 | setInstance(res); 42 | 43 | return res; 44 | }); 45 | } 46 | 47 | if (applicationId && locationId) { 48 | loadPayment(signal); 49 | } 50 | 51 | return () => { 52 | abortController.abort(); 53 | }; 54 | }, [applicationId, locationId]); 55 | 56 | const cardTokenizeResponseReceived = async (rest: Square.TokenResult): Promise => { 57 | if (rest.errors || !props.createVerificationDetails) { 58 | await props.cardTokenizeResponseReceived(rest); 59 | return; 60 | } 61 | 62 | const verifyBuyerResults = await instance?.verifyBuyer(String(rest.token), props.createVerificationDetails()); 63 | 64 | await props.cardTokenizeResponseReceived(rest, verifyBuyerResults); 65 | }; 66 | 67 | // Fixes stale closure issue with using React Hooks & SqPaymentForm callback functions 68 | // https://github.com/facebook/react/issues/16956 69 | const cardTokenizeResponseReceivedCallback = useDynamicCallback(cardTokenizeResponseReceived); 70 | 71 | if (!applicationId || !locationId) { 72 | return ; 73 | } 74 | 75 | if (!instance) return null; 76 | 77 | const context: FormContextType = { 78 | cardTokenizeResponseReceived: cardTokenizeResponseReceivedCallback, 79 | createPaymentRequest, 80 | payments: instance, 81 | }; 82 | 83 | return {children}; 84 | } 85 | 86 | const useForm = (): FormContextType => { 87 | const context = React.useContext(FormContext); 88 | 89 | if (context === undefined) { 90 | throw new Error('useForm must be used within a FormProvider'); 91 | } 92 | 93 | return context; 94 | }; 95 | 96 | export { FormContext, useForm }; 97 | export default FormProvider; 98 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for reporting an issue :pray:. 8 | 9 | This issue tracker is for reporting bugs found in `react-square-web-payments-sdk` (https://github.com/weareseeed/react-square-web-payments-sdk). 10 | If you have a question about how to achieve something and are struggling, please post a question 11 | inside of `react-square-web-payments-sdk` Discussions tab: https://github.com/weareseeed/react-square-web-payments-sdk/discussions 12 | 13 | Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: 14 | - `react-square-web-payments-sdk` Issues tab: https://github.com/weareseeed/react-square-web-payments-sdk/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 15 | - `react-square-web-payments-sdk` closed issues tab: https://github.com/weareseeed/react-square-web-payments-sdk/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed 16 | - `react-square-web-payments-sdk` Discussions tab: https://github.com/weareseeed/react-square-web-payments-sdk/discussions 17 | 18 | The more information you fill in, the better the community can help you. 19 | - type: textarea 20 | id: description 21 | attributes: 22 | label: Describe the bug 23 | description: Provide a clear and concise description of the challenge you are running into. 24 | validations: 25 | required: true 26 | - type: input 27 | id: link 28 | attributes: 29 | label: Your Example Website or App 30 | description: | 31 | Which website or app were you using when the bug happened? 32 | Note: 33 | - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the `react-square-web-payments-sdk` npm package. 34 | - To create a shareable code example you can use Stackblitz (https://stackblitz.com/). Please no localhost URLs. 35 | - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve. 36 | placeholder: | 37 | e.g. https://stackblitz.com/edit/...... OR Github Repo 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: steps 42 | attributes: 43 | label: Steps to Reproduce the Bug or Issue 44 | description: Describe the steps we have to take to reproduce the behavior. 45 | placeholder: | 46 | 1. Go to '...' 47 | 2. Click on '....' 48 | 3. Scroll down to '....' 49 | 4. See error 50 | validations: 51 | required: true 52 | - type: textarea 53 | id: expected 54 | attributes: 55 | label: Expected behavior 56 | description: Provide a clear and concise description of what you expected to happen. 57 | placeholder: | 58 | As a user, I expected ___ behavior but i am seeing ___ 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: screenshots_or_videos 63 | attributes: 64 | label: Screenshots or Videos 65 | description: | 66 | If applicable, add screenshots or a video to help explain your problem. 67 | For more information on the supported file image/file types and the file size limits, please refer 68 | to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files 69 | placeholder: | 70 | You can drag your video or image files inside of this editor ↓ 71 | - type: textarea 72 | id: platform 73 | attributes: 74 | label: Platform 75 | value: | 76 | - OS: [e.g. macOS, Windows, Linux] 77 | - Browser: [e.g. Chrome, Safari, Firefox] 78 | - Version: [e.g. 91.1] 79 | validations: 80 | required: true 81 | - type: textarea 82 | id: additional 83 | attributes: 84 | label: Additional context 85 | description: Add any other context about the problem here. 86 | -------------------------------------------------------------------------------- /src/components/apple-pay/apple-pay.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useForm } from '~/contexts/form'; 7 | import { useEventListener } from '~/hooks/use-event-listener'; 8 | import { ApplePayContainer } from './apple-pay.styles'; 9 | import type { ApplePayProps } from './apple-pay.types'; 10 | 11 | /** 12 | * Renders a Apple Pay button to use in the Square Web Payment SDK, pre-styled 13 | * to meet Apple Pay's branding guidelines. 14 | * 15 | * **Remember** that you need to set `createPaymentRequest()` in `SquareForm` if 16 | * you going to use this Payment Method 17 | * 18 | * @example 19 | * 20 | * ```tsx 21 | * function App() { 22 | * return ( 23 | * 24 | * 25 | * 26 | * ); 27 | * } 28 | * ``` 29 | */ 30 | function ApplePay({ id = 'rswps-apple-pay', ...props }: ApplePayProps) { 31 | const [applePay, setApplePay] = React.useState(() => undefined); 32 | const { cardTokenizeResponseReceived, createPaymentRequest, payments } = useForm(); 33 | const containerRef = React.useRef(null); 34 | 35 | /** 36 | * Handle the on click of the Apple Pay button click 37 | * 38 | * @param e An event which takes place in the DOM. 39 | * @returns The data be sended to `cardTokenizeResponseReceived()` function, or an error 40 | */ 41 | const handlePayment = async (e: Event) => { 42 | e.stopPropagation(); 43 | 44 | if (!applePay) { 45 | console.warn('Apple Pay button was clicked, but no Apple Pay instance was found.'); 46 | 47 | return; 48 | } 49 | 50 | try { 51 | const result = await applePay.tokenize(); 52 | 53 | if (result.status === 'OK') { 54 | return cardTokenizeResponseReceived(result); 55 | } 56 | 57 | let message = `Tokenization failed with status: ${result.status}`; 58 | if (result?.errors) { 59 | message += ` and errors: ${JSON.stringify(result?.errors)}`; 60 | 61 | throw new Error(message); 62 | } 63 | 64 | console.warn(message); 65 | } catch (error) { 66 | console.error(error); 67 | } 68 | }; 69 | 70 | React.useEffect(() => { 71 | if (!createPaymentRequest) { 72 | throw new Error('`createPaymentRequest()` is required when using digital wallets'); 73 | } 74 | 75 | const abortController = new AbortController(); 76 | const { signal } = abortController; 77 | 78 | const start = async (signal: AbortSignal) => { 79 | const paymentRequest = payments?.paymentRequest(createPaymentRequest); 80 | 81 | if (!paymentRequest) { 82 | throw new Error('`paymentRequest` is required when using digital wallets'); 83 | } 84 | 85 | try { 86 | const applePay = await payments?.applePay(paymentRequest).then((res) => { 87 | if (signal?.aborted) { 88 | return; 89 | } 90 | 91 | setApplePay(res); 92 | 93 | return res; 94 | }); 95 | 96 | if (signal.aborted) { 97 | await applePay?.destroy(); 98 | } 99 | } catch (error) { 100 | console.error('Initializing Apple Pay failed', error); 101 | } 102 | }; 103 | 104 | start(signal); 105 | 106 | return () => { 107 | abortController.abort(); 108 | }; 109 | }, [createPaymentRequest, payments]); 110 | 111 | useEventListener({ 112 | listener: handlePayment, 113 | type: 'click', 114 | element: containerRef, 115 | options: { 116 | passive: true, 117 | }, 118 | }); 119 | 120 | return ( 121 | 133 | ); 134 | } 135 | 136 | export default ApplePay; 137 | export * from './apple-pay.types'; 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,node,react 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,node,react 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### Node ### 50 | # Logs 51 | logs 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | lerna-debug.log* 57 | .pnpm-debug.log* 58 | 59 | # Diagnostic reports (https://nodejs.org/api/report.html) 60 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 61 | 62 | # Runtime data 63 | pids 64 | *.pid 65 | *.seed 66 | *.pid.lock 67 | 68 | # Directory for instrumented libs generated by jscoverage/JSCover 69 | lib-cov 70 | 71 | # Coverage directory used by tools like istanbul 72 | coverage 73 | *.lcov 74 | 75 | # nyc test coverage 76 | .nyc_output 77 | 78 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 79 | .grunt 80 | 81 | # Bower dependency directory (https://bower.io/) 82 | bower_components 83 | 84 | # node-waf configuration 85 | .lock-wscript 86 | 87 | # Compiled binary addons (https://nodejs.org/api/addons.html) 88 | build/Release 89 | 90 | # Dependency directories 91 | node_modules/ 92 | jspm_packages/ 93 | 94 | # Snowpack dependency directory (https://snowpack.dev/) 95 | web_modules/ 96 | 97 | # TypeScript cache 98 | *.tsbuildinfo 99 | 100 | # Optional npm cache directory 101 | .npm 102 | 103 | # Optional eslint cache 104 | .eslintcache 105 | 106 | # Microbundle cache 107 | .rpt2_cache/ 108 | .rts2_cache_cjs/ 109 | .rts2_cache_es/ 110 | .rts2_cache_umd/ 111 | 112 | # Optional REPL history 113 | .node_repl_history 114 | 115 | # Output of 'npm pack' 116 | *.tgz 117 | 118 | # Yarn Integrity file 119 | .yarn-integrity 120 | 121 | # dotenv environment variables file 122 | .env 123 | .env.test 124 | .env.production 125 | 126 | # parcel-bundler cache (https://parceljs.org/) 127 | .cache 128 | .parcel-cache 129 | 130 | # Next.js build output 131 | .next 132 | out 133 | 134 | # Nuxt.js build / generate output 135 | .nuxt 136 | dist 137 | 138 | # Gatsby files 139 | .cache/ 140 | # Comment in the public line in if your project uses Gatsby and not Next.js 141 | # https://nextjs.org/blog/next-9-1#public-directory-support 142 | # public 143 | 144 | # vuepress build output 145 | .vuepress/dist 146 | 147 | # Serverless directories 148 | .serverless/ 149 | 150 | # FuseBox cache 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | .dynamodb/ 155 | 156 | # TernJS port file 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | .vscode-test 161 | 162 | # yarn v2 163 | .yarn/cache 164 | .yarn/unplugged 165 | .yarn/build-state.yml 166 | .yarn/install-state.gz 167 | .pnp.* 168 | 169 | ### Node Patch ### 170 | # Serverless Webpack directories 171 | .webpack/ 172 | 173 | # Optional stylelint cache 174 | .stylelintcache 175 | 176 | # SvelteKit build / generate output 177 | .svelte-kit 178 | 179 | ### react ### 180 | .DS_* 181 | **/*.backup.* 182 | **/*.back.* 183 | 184 | node_modules 185 | 186 | *.sublime* 187 | 188 | psd 189 | thumb 190 | sketch 191 | 192 | ### Windows ### 193 | # Windows thumbnail cache files 194 | Thumbs.db 195 | Thumbs.db:encryptable 196 | ehthumbs.db 197 | ehthumbs_vista.db 198 | 199 | # Dump file 200 | *.stackdump 201 | 202 | # Folder config file 203 | [Dd]esktop.ini 204 | 205 | # Recycle Bin used on file shares 206 | $RECYCLE.BIN/ 207 | 208 | # Windows Installer files 209 | *.cab 210 | *.msi 211 | *.msix 212 | *.msm 213 | *.msp 214 | 215 | # Windows shortcuts 216 | *.lnk 217 | 218 | # End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,node,react -------------------------------------------------------------------------------- /src/components/google-pay/google-pay.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useForm } from '~/contexts/form'; 7 | import { useEventListener } from '~/hooks/use-event-listener'; 8 | import { ButtonLoader } from './google-pay.styles'; 9 | import type { GooglePayProps } from './google-pay.types'; 10 | 11 | /** 12 | * Renders a Google Pay button to use in the Square Web Payment SDK, pre-styled 13 | * to meet Google's branding guidelines. 14 | * 15 | * **Remember** that you need to set `createPaymentRequest()` in `SquareForm` if 16 | * you going to use this Payment Method 17 | * 18 | * @example 19 | * 20 | * ```tsx 21 | * function App() { 22 | * return ( 23 | * 24 | * 25 | * 26 | * ); 27 | * } 28 | * ``` 29 | */ 30 | const GooglePay = ({ 31 | buttonColor, 32 | buttonSizeMode = 'fill', 33 | buttonType = 'long', 34 | id = 'rswps-google-pay-container', 35 | ...props 36 | }: GooglePayProps): JSX.Element | null => { 37 | const [googlePay, setGooglePay] = React.useState(() => undefined); 38 | const { cardTokenizeResponseReceived, createPaymentRequest, payments } = useForm(); 39 | const containerRef = React.useRef(null); 40 | 41 | const options: Square.GooglePayButtonOptions = React.useMemo(() => { 42 | const baseOptions = { 43 | buttonColor, 44 | buttonSizeMode, 45 | buttonType, 46 | }; 47 | 48 | // if a value from options is undefined delete it from the options object 49 | return Object.keys(baseOptions).reduce((acc: Record, key) => { 50 | if (baseOptions[key as keyof typeof baseOptions] !== undefined) { 51 | acc[key as string] = baseOptions[key as keyof typeof baseOptions]; 52 | } 53 | 54 | return acc; 55 | }, {}); 56 | }, [buttonColor, buttonSizeMode, buttonType]); 57 | 58 | /** 59 | * Handle the on click of the Google Pay button click 60 | * 61 | * @param e An event which takes place in the DOM. 62 | * @returns The data be sended to `cardTokenizeResponseReceived()` function, or an error 63 | */ 64 | const handlePayment = async (e: Event) => { 65 | e.stopPropagation(); 66 | 67 | if (!googlePay) { 68 | console.warn('Google Pay button was clicked, but no Google Pay instance was found.'); 69 | 70 | return; 71 | } 72 | 73 | try { 74 | const result = await googlePay.tokenize(); 75 | 76 | if (result.status === 'OK') { 77 | return cardTokenizeResponseReceived(result); 78 | } 79 | 80 | let message = `Tokenization failed with status: ${result.status}`; 81 | if (result?.errors) { 82 | message += ` and errors: ${JSON.stringify(result?.errors)}`; 83 | 84 | throw new Error(message); 85 | } 86 | 87 | console.warn(message); 88 | } catch (error) { 89 | console.error(error); 90 | } 91 | }; 92 | 93 | React.useEffect(() => { 94 | if (!createPaymentRequest) { 95 | throw new Error('`createPaymentRequest()` is required when using digital wallets'); 96 | } 97 | 98 | const abortController = new AbortController(); 99 | const { signal } = abortController; 100 | 101 | const start = async (signal: AbortSignal) => { 102 | const paymentRequest = payments?.paymentRequest(createPaymentRequest); 103 | 104 | if (!paymentRequest) { 105 | throw new Error('`paymentRequest` is required when using digital wallets'); 106 | } 107 | 108 | try { 109 | const googlePay = await payments?.googlePay(paymentRequest).then((res) => { 110 | if (signal?.aborted) { 111 | return; 112 | } 113 | 114 | setGooglePay(res); 115 | 116 | return res; 117 | }); 118 | 119 | await googlePay?.attach(`#${id}`, options); 120 | 121 | if (signal.aborted) { 122 | await googlePay?.destroy(); 123 | } 124 | } catch (error) { 125 | console.error('Initializing Google Pay failed', error); 126 | } 127 | }; 128 | 129 | start(signal); 130 | 131 | return () => { 132 | abortController.abort(); 133 | }; 134 | }, [createPaymentRequest, payments, options]); 135 | 136 | useEventListener({ 137 | listener: handlePayment, 138 | type: 'click', 139 | element: containerRef, 140 | options: { 141 | passive: true, 142 | }, 143 | }); 144 | 145 | return ( 146 |
147 | {!googlePay ? : null} 148 |
149 | ); 150 | }; 151 | 152 | export default GooglePay; 153 | export * from './google-pay.types'; 154 | -------------------------------------------------------------------------------- /src/contexts/form/form.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | 4 | export type FormContextType = { 5 | /** 6 | * **Required for all features** 7 | * 8 | * Invoked when payment form receives the result of a tokenize generation 9 | * request. The result will be a valid credit card or wallet token, or an error. 10 | */ 11 | cardTokenizeResponseReceived: ( 12 | token: Square.TokenResult, 13 | verifiedBuyer?: Square.VerifyBuyerResponseDetails | null 14 | ) => Promise; 15 | /** 16 | * Returned by `Square.payments(appId, locationId)`. 17 | * 18 | * Use this object to instantiate Payment methods. 19 | * 20 | * @example 21 | * 22 | * ```js 23 | * const payments = Square.payments(appId, locationId); 24 | * ``` 25 | */ 26 | payments: Square.Payments | null; 27 | /** 28 | * **Required for digital wallets** 29 | * 30 | * Invoked when a digital wallet payment button is clicked. 31 | */ 32 | createPaymentRequest?: Square.PaymentRequestOptions; 33 | }; 34 | 35 | export type FormProviderProps = { 36 | /** 37 | * Identifies the calling form with a verified application ID generated from 38 | * the Square Application Dashboard. 39 | */ 40 | applicationId: string; 41 | /** 42 | * Invoked when payment form receives the result of a tokenize generation 43 | * request. The result will be a valid credit card or wallet token, or an error. 44 | */ 45 | cardTokenizeResponseReceived: ( 46 | token: Square.TokenResult, 47 | verifiedBuyer?: Square.VerifyBuyerResponseDetails | null 48 | ) => void | Promise; 49 | children: React.ReactNode; 50 | /** 51 | * Identifies the location of the merchant that is taking the payment. 52 | * Obtained from the Square Application Dashboard - Locations tab. 53 | */ 54 | locationId: string; 55 | /** 56 | * The payments.paymentRequest method argument 57 | * 58 | * This object contains the details of a payment request including line items, 59 | * shipping contact and options, and total payment request. 60 | * 61 | * @example 62 | * 63 | * ```js 64 | * () => ({ 65 | * countryCode: 'US', 66 | * currencyCode: 'USD', 67 | * lineItems: [ 68 | * { 69 | * amount: '22.15', 70 | * label: 'Item to be purchased', 71 | * id: 'SKU-12345', 72 | * imageUrl: 'https://url-cdn.com/123ABC', 73 | * pending: true, 74 | * productUrl: 'https://my-company.com/product-123ABC', 75 | * }, 76 | * ], 77 | * taxLineItems: [ 78 | * { 79 | * label: 'State Tax', 80 | * amount: '8.95', 81 | * pending: true, 82 | * }, 83 | * ], 84 | * discounts: [ 85 | * { 86 | * label: 'Holiday Discount', 87 | * amount: '5.00', 88 | * pending: true, 89 | * }, 90 | * ], 91 | * requestBillingContact: false, 92 | * requestShippingContact: false, 93 | * shippingOptions: [ 94 | * { 95 | * label: 'Next Day', 96 | * amount: '15.69', 97 | * id: '1', 98 | * }, 99 | * { 100 | * label: 'Three Day', 101 | * amount: '2.00', 102 | * id: '2', 103 | * }, 104 | * ], 105 | * // pending is only required if it's true. 106 | * total: { 107 | * amount: '41.79', 108 | * label: 'Total', 109 | * }, 110 | * }); 111 | * ``` 112 | */ 113 | createPaymentRequest?: () => Square.PaymentRequestOptions; 114 | /** 115 | * Can be either a charge or a store. 116 | * 117 | * **Charge:** cases in which the buyer is being charged. 118 | * 119 | * **Store:** cases in which the card is being stored on file. 120 | * 121 | * @example 122 | * 123 | * ```js 124 | * const chargeVerificationDetails = { 125 | * amount: '1.00', 126 | * currencyCode: 'GBP', 127 | * intent: 'CHARGE', 128 | * billingContact: { 129 | * addressLines: ['123 Main Street', 'Apartment 1'], 130 | * familyName: 'Doe', 131 | * givenName: 'John', 132 | * email: 'jondoe@gmail.com', 133 | * country: 'GB', 134 | * phone: '3214563987', 135 | * region: 'LND', 136 | * city: 'London', 137 | * }, 138 | * }; 139 | * 140 | * const storeVerificationDetails = { 141 | * intent: 'STORE', 142 | * billingContact: { 143 | * addressLines: ['123 Main Street', 'Apartment 1'], 144 | * familyName: 'Doe', 145 | * givenName: 'John', 146 | * email: 'jondoe@gmail.com', 147 | * country: 'GB', 148 | * phone: '3214563987', 149 | * region: 'LND', 150 | * city: 'London', 151 | * }, 152 | * }; 153 | * ``` 154 | */ 155 | createVerificationDetails?: () => Square.ChargeVerifyBuyerDetails | Square.StoreVerifyBuyerDetails; 156 | /** Override the default payment form configuration. */ 157 | overrides?: { 158 | /** The URL of the Square payment form script. */ 159 | scriptSrc?: string; 160 | }; 161 | }; 162 | -------------------------------------------------------------------------------- /src/components/gift-card/gift-card.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | import type * as Stitches from '@stitches/react'; 4 | 5 | // Internals 6 | import { PayButton } from './gift-card.styles'; 7 | 8 | export type GiftCardPayButtonProps = Omit< 9 | React.ComponentPropsWithoutRef<'button'>, 10 | 'aria-disabled' | 'disabled' | 'type' 11 | > & { 12 | /** 13 | * Sets the style for the Payment Button using a CSS object 14 | * 15 | * @example 16 | * 17 | * ```js 18 | * const overrideStyles = { 19 | * background: 'white', 20 | * '&:hover': { 21 | * background: 'rgba(1, 208, 158, 0.1)', 22 | * }, 23 | * }; 24 | * ``` 25 | */ 26 | css?: Stitches.ComponentProps['css']; 27 | /** Control the loading state of the button a.k.a disabling the button. */ 28 | isLoading?: boolean; 29 | }; 30 | 31 | export interface GiftCardBase 32 | extends Square.GiftCardOptions, 33 | Omit, 'style' | 'children'> { 34 | /** 35 | * Props to be passed to the ` 186 | )} 187 | 188 | ); 189 | } 190 | 191 | export default GiftCard; 192 | export * from './gift-card.types'; 193 | -------------------------------------------------------------------------------- /src/components/afterpay/afterpay.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useAfterpay } from '~/contexts/afterpay'; 7 | import { useEventListener } from '~/hooks/use-event-listener'; 8 | import { AfterpayContext, AfterpayProvider } from '~/contexts/afterpay'; 9 | import { useForm } from '~/contexts/form'; 10 | import { ButtonLoader } from './afterpay.styles'; 11 | import type { 12 | AfterpayButtonProps, 13 | AfterpayMessageBaseProps, 14 | AfterpayMessageCustomComponentProps, 15 | AfterpayMessageProps, 16 | AfterpayWidgetProps, 17 | } from './afterpay.types'; 18 | 19 | export function AfterpayButton({ 20 | Button, 21 | buttonColor = 'black', 22 | buttonType = 'buy_now_with_afterpay', 23 | finalCtaButtonType = 'buy_now', 24 | id = 'rswps-afterpay-button', 25 | ...props 26 | }: AfterpayButtonProps) { 27 | const containerRef = React.useRef(null); 28 | const afterpay = useAfterpay(); 29 | const { cardTokenizeResponseReceived } = useForm(); 30 | 31 | const options: Square.AfterpayButtonOptions = React.useMemo( 32 | () => ({ 33 | buttonColor, 34 | buttonType, 35 | finalCtaButtonType, 36 | useCustomButton: Boolean(Button), 37 | }), 38 | [Button, buttonColor, buttonType, finalCtaButtonType] 39 | ); 40 | 41 | /** 42 | * Handle the on click of the Afterpay button click 43 | * 44 | * @param e An event which takes place in the DOM. 45 | * @returns The data be sended to `cardTokenizeResponseReceived()` function, or an error 46 | */ 47 | const handlePayment = async (e: Event) => { 48 | e.stopPropagation(); 49 | 50 | if (!afterpay) { 51 | console.warn('Afterpay/Clearpay button was clicked, but no Afterpay/Clearpay instance was found.'); 52 | 53 | return; 54 | } 55 | 56 | try { 57 | const result = await afterpay.tokenize(); 58 | 59 | if (result.status === 'OK') { 60 | return cardTokenizeResponseReceived(result); 61 | } 62 | 63 | let message = `Tokenization failed with status: ${result?.status}`; 64 | if (result?.errors) { 65 | message += ` and errors: ${JSON.stringify(result?.errors)}`; 66 | 67 | throw new Error(message); 68 | } 69 | 70 | console.warn(message); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }; 75 | 76 | React.useEffect(() => { 77 | if (afterpay) { 78 | afterpay?.attach(`#${id}`, options); 79 | } 80 | 81 | return () => { 82 | if (afterpay) { 83 | afterpay?.destroy(); 84 | } 85 | }; 86 | }, [afterpay]); 87 | 88 | useEventListener({ 89 | listener: handlePayment, 90 | type: 'click', 91 | element: containerRef, 92 | options: { 93 | passive: true, 94 | }, 95 | }); 96 | 97 | if (Button) { 98 | return } 200 | 201 | ); 202 | } 203 | 204 | export default CreditCard; 205 | export * from './credit-card.types'; 206 | -------------------------------------------------------------------------------- /src/components/ach/ach.tsx: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import * as React from 'react'; 3 | import type * as Square from '@square/web-sdk'; 4 | 5 | // Internals 6 | import { useForm } from '~/contexts/form'; 7 | import { useEventListener } from '~/hooks/use-event-listener'; 8 | import { PayButton, SvgIcon } from './ach.styles'; 9 | import { transformPlaidEventName } from './ach.utils'; 10 | import type { AchProps, } from './ach.types'; 11 | 12 | /** 13 | * Renders a ACH button to use in the Square Web Payment SDK, pre-styled to meet 14 | * Square branding guidelines. 15 | * 16 | * **_But with the option to override styles or use a custom children_** 17 | * 18 | * @example 19 | * 20 | * ```tsx 21 | * function App() { 22 | * return ( 23 | * 24 | * 25 | * 26 | * ); 27 | * } 28 | * ``` 29 | */ 30 | export function Ach({ 31 | accountHolderName, 32 | redirectURI, 33 | transactionId, 34 | callbacks, 35 | buttonProps, 36 | children, 37 | svgProps, 38 | }: AchProps) { 39 | const [ach, setAch] = React.useState(() => undefined); 40 | const [isSubmitting, setIsSubmitting] = React.useState(false); 41 | const { cardTokenizeResponseReceived, createPaymentRequest, payments } = useForm(); 42 | const buttonRef = React.useRef(null); 43 | 44 | /** 45 | * Handle the on click of the ACH button click 46 | * 47 | * @param e An event which takes place in the DOM. 48 | * @returns The data be sended to `cardTokenizeResponseReceived()` function, or an error 49 | */ 50 | const handlePayment = async (e: Event) => { 51 | e.stopPropagation(); 52 | 53 | if (!ach) { 54 | console.warn('ACH button was clicked, but no ACH instance was found.'); 55 | return; 56 | } 57 | 58 | if (!createPaymentRequest) throw new Error('`createPaymentRequest()` is required when using ACH payments'); 59 | 60 | setIsSubmitting(true); 61 | 62 | try { 63 | const result = await ach.tokenize({ 64 | accountHolderName, 65 | intent: 'CHARGE', 66 | amount: createPaymentRequest.total.amount, 67 | currency: createPaymentRequest.currencyCode, 68 | }); 69 | 70 | if (result?.status === 'OK') { 71 | const tokenizedResult = await cardTokenizeResponseReceived(result); 72 | return tokenizedResult; 73 | } 74 | 75 | let message = `Tokenization failed with status: ${result?.status ?? ''}`; 76 | if (result?.errors) { 77 | message += ` and errors: ${JSON.stringify(result?.errors)}`; 78 | throw new Error(message); 79 | } 80 | 81 | console.warn(message); 82 | } catch (error) { 83 | console.error(error); 84 | } finally { 85 | setIsSubmitting(false); 86 | } 87 | }; 88 | 89 | React.useEffect(() => { 90 | const abortController = new AbortController(); 91 | const { signal } = abortController; 92 | 93 | const start = async (signal: AbortSignal) => { 94 | const ach = await payments 95 | ?.ach({ 96 | redirectURI, 97 | transactionId, 98 | }) 99 | .then((res) => { 100 | if (signal?.aborted) return; 101 | setAch(res); 102 | return res; 103 | }); 104 | 105 | if (signal.aborted) { 106 | ach?.removeEventListener('ontokenization' as Square.PlaidEventName, () => { }) 107 | await ach?.destroy(); 108 | } else { 109 | ach?.addEventListener( 110 | 'ontokenization' as Square.PlaidEventName, 111 | async (result: Square.SqEvent) => { 112 | const { tokenResult, error } = result.detail; 113 | if (error) { 114 | // add code here to handle error 115 | } else if (tokenResult?.status == 'OK') { 116 | await cardTokenizeResponseReceived(tokenResult); 117 | } 118 | } 119 | ) 120 | } 121 | }; 122 | 123 | start(signal); 124 | 125 | return () => { 126 | abortController.abort(); 127 | }; 128 | }, [createPaymentRequest, payments]); 129 | 130 | if (callbacks) { 131 | for (const callback of Object.keys(callbacks)) { 132 | ach?.addEventListener( 133 | transformPlaidEventName(callback), 134 | (callbacks as Record) => void>)[callback] 135 | ); 136 | } 137 | } 138 | 139 | 140 | useEventListener({ 141 | listener: handlePayment, 142 | type: 'click', 143 | element: buttonRef, 144 | options: { 145 | passive: true, 146 | }, 147 | }); 148 | 149 | const { isLoading, ...props } = buttonProps ?? {}; 150 | const disabled = isLoading || !ach || isSubmitting; 151 | 152 | if (children) { 153 | return ( 154 | 155 | {children} 156 | 157 | ); 158 | } 159 | 160 | return ( 161 | 162 | 170 | 171 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Pay with Direct debit (ACH) 185 | 186 | ); 187 | } 188 | 189 | export default Ach; 190 | export * from './ach.types'; 191 | -------------------------------------------------------------------------------- /src/components/credit-card/credit-card.types.ts: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import type * as Square from '@square/web-sdk'; 3 | import type * as Stitches from '@stitches/react'; 4 | import type * as React from 'react'; 5 | 6 | // Internals 7 | import { PayButton } from './credit-card.styles'; 8 | 9 | export type CreditCardPayButtonProps = Omit< 10 | React.ComponentPropsWithoutRef<'button'>, 11 | 'aria-disabled' | 'disabled' | 'type' 12 | > & { 13 | /** 14 | * Sets the style for the Payment Button using a CSS object 15 | * 16 | * @example 17 | * 18 | * ```js 19 | * const overrideStyles = { 20 | * background: 'white', 21 | * '&:hover': { 22 | * background: 'rgba(1, 208, 158, 0.1)', 23 | * }, 24 | * }; 25 | * ``` 26 | */ 27 | css?: Stitches.ComponentProps['css']; 28 | /** Control the loading state of the button a.k.a disabling the button. */ 29 | isLoading?: boolean; 30 | }; 31 | 32 | export type CreditCardFunctionChildrenProps = { 33 | Button: (props: CreditCardPayButtonProps) => React.ReactElement; 34 | }; 35 | 36 | export interface CreditCardBase 37 | extends Square.CardOptions, 38 | Omit, 'style' | 'children'> { 39 | callbacks?: { 40 | /** 41 | * Callback function that is called when the payment form detected a new 42 | * likely credit card brand based on the card number. 43 | */ 44 | cardBrandChanged?(event: Square.SqEvent): void; 45 | /** 46 | * Callback function that is called when a form field has an invalid value, 47 | * and the corresponding error CSS class was added to the element. 48 | */ 49 | errorClassAdded?(event: Square.SqEvent): void; 50 | /** 51 | * Callback function that is called when an invalid value on a form field 52 | * was corrected, and the corresponding error CSS class was removed from the element 53 | */ 54 | errorClassRemoved?(event: Square.SqEvent): void; 55 | /** 56 | * Callback function that is called when the user pressed the "Escape" key 57 | * while editing a field. 58 | */ 59 | escape?(event: Square.SqEvent): void; 60 | /** 61 | * Callback function that is called when a form field gained focus, and the 62 | * corresponding focus CSS class was added to the element. 63 | */ 64 | focusClassAdded?(event: Square.SqEvent): void; 65 | /** 66 | * Callback function that is called when a form field lost focus, and the 67 | * corresponding focus CSS class was removed from the element. 68 | */ 69 | focusClassRemoved?(event: Square.SqEvent): void; 70 | /** 71 | * Callback function that is called when the current value of the postal 72 | * code form field changed. 73 | */ 74 | postalCodeChanged?(event: Square.SqEvent): void; 75 | /** 76 | * Callback function that is called when the user has triggered submission 77 | * of the form by pressing "Enter" while editing a field. 78 | */ 79 | submit?(event: Square.SqEvent): void; 80 | }; 81 | /** 82 | * Sets the DOM focus of one of the input fields within the credit card form. 83 | * 84 | * For more details about the available options, see 85 | * [CardFieldNames](https://developer.squareup.com/reference/sdks/web/payments/enums/CardFieldNames). 86 | * 87 | * @throws {InvalidFieldNameError} The specified field name is invalid 88 | */ 89 | focus?: Square.CardFieldNamesValues; 90 | /** 91 | * Recalculates the size of the card form. 92 | * 93 | * The Card component normally automatically resizes based on the size of the 94 | * buyer's browser, however if the Card component is contained with an element 95 | * that has a computed style property of "display: none", then the Card 96 | * component will no longer have a defined width and therefore will not 97 | * properly resize between mobile and desktop configurations. Upon being 98 | * displayed again, the Card component will not automatically update its size 99 | * to match the browser window. 100 | * 101 | * This method recalculateSize() can be used to handle this edge case by 102 | * forcing the Card component to recalculate its size and display 103 | * appropriately for mobile or desktop. 104 | * 105 | * @example Card.recalculateSize() 106 | * 107 | * @throws {PaymentMethodNotAttachedError} `Card` is has not been attached to 108 | * a DOM element 109 | */ 110 | recalculateSize?(callback: Square.Card['recalculateSize'] | undefined): void; 111 | } 112 | 113 | export interface CreditCardChildren extends CreditCardBase { 114 | /** 115 | * Props to be passed to the `