├── .husky
├── post-checkout
├── post-merge
├── post-rewrite
├── pre-commit
└── pre-push
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .stylelintignore
├── .stylelintrc.json
├── .vscode
├── launch.json
└── tasks.json
├── SECURITY.md
├── apple-developer-merchantid-domain-association
├── assets
├── css
│ ├── stripe-link.css
│ ├── stripe-link.css.map
│ ├── stripe-styles.css
│ └── stripe-styles.css.map
├── images
│ ├── affirm.svg
│ ├── afterpay.svg
│ ├── alipay.svg
│ ├── amex.svg
│ ├── bancontact.svg
│ ├── bank-debit.svg
│ ├── blik.svg
│ ├── boleto.svg
│ ├── cards.svg
│ ├── cashapp.svg
│ ├── clearpay.svg
│ ├── credit-card.svg
│ ├── diners.svg
│ ├── discover.svg
│ ├── eps.svg
│ ├── giropay.svg
│ ├── ideal.svg
│ ├── jcb.svg
│ ├── klarna.svg
│ ├── link.svg
│ ├── maestro.svg
│ ├── mastercard.svg
│ ├── multibanco.svg
│ ├── oxxo.svg
│ ├── p24.svg
│ ├── sepa.svg
│ ├── sofort.svg
│ ├── stripe.svg
│ ├── visa.svg
│ └── wechat.svg
└── js
│ ├── jquery.mask.js
│ ├── jquery.mask.min.js
│ ├── stripe-payment-request.js
│ ├── stripe-payment-request.min.js
│ ├── stripe.js
│ └── stripe.min.js
├── babel.config.js
├── changelog.txt
├── client
├── api
│ ├── blocks.js
│ └── index.js
├── blocks
│ ├── __tests__
│ │ ├── index.test.js
│ │ └── utils.test.js
│ ├── credit-card
│ │ ├── constants.js
│ │ ├── elements.js
│ │ ├── index.js
│ │ ├── payment-method.js
│ │ ├── use-checkout-subscriptions.js
│ │ ├── use-element-options.js
│ │ └── use-payment-processing.js
│ ├── express-checkout
│ │ ├── constants.js
│ │ ├── express-button-previews
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ ├── express-checkout-component.js
│ │ ├── express-checkout-container.js
│ │ ├── hooks.js
│ │ └── index.js
│ ├── index.js
│ ├── load-stripe.js
│ ├── normalize.js
│ ├── payment-request
│ │ ├── apple-pay-preview.js
│ │ ├── branded-buttons.js
│ │ ├── constants.js
│ │ ├── custom-button.js
│ │ ├── event-handlers.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── login-confirmation.js
│ │ └── payment-request-express.js
│ ├── three-d-secure
│ │ ├── index.js
│ │ ├── three-d-secure-payment-handler.js
│ │ └── use-payment-intents.js
│ ├── upe
│ │ ├── call-when-element-is-available.js
│ │ ├── checkout-icons.js
│ │ ├── confirm-card-payment.js
│ │ ├── confirm-upe-payment.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── saved-token-handler.js
│ │ ├── styles.scss
│ │ ├── token-label-updater.js
│ │ ├── upe-deferred-intent-creation
│ │ │ ├── blik-code-element.js
│ │ │ ├── payment-elements.js
│ │ │ └── payment-processor.js
│ │ └── upe-element.js
│ └── utils.js
├── brand-logos
│ ├── stripe-mark
│ │ ├── index.js
│ │ └── mark.svg
│ └── woo-white
│ │ ├── index.js
│ │ └── logo.svg
├── classic
│ └── upe
│ │ ├── deferred-intent.js
│ │ ├── index.js
│ │ ├── legacy-support.js
│ │ ├── payment-processing.js
│ │ └── style.scss
├── components
│ ├── chip
│ │ ├── index.js
│ │ └── styles.scss
│ ├── confirmation-modal
│ │ ├── alert-title.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── inline-notice
│ │ ├── index.js
│ │ └── style.scss
│ ├── loadable
│ │ ├── index.js
│ │ ├── style.scss
│ │ └── test
│ │ │ ├── __snapshots__
│ │ │ └── index.js.snap
│ │ │ └── index.js
│ ├── payment-method-capability-status-pill
│ │ ├── __tests__
│ │ │ └── payment-method-capability-status-pill.test.js
│ │ └── index.js
│ ├── payment-method-deprecation-pill
│ │ ├── __tests__
│ │ │ └── payment-method-deprecation-pill.test.js
│ │ └── index.js
│ ├── payment-method-fees-pill
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ └── index.js
│ ├── payment-method-missing-currency-pill
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ └── index.js
│ ├── pill
│ │ └── index.js
│ ├── popover
│ │ ├── index.js
│ │ └── test
│ │ │ └── index.test.js
│ ├── recurring-payment-icon
│ │ ├── icon.svg
│ │ └── index.js
│ ├── stripe-banner
│ │ ├── icon.svg
│ │ └── index.js
│ ├── tooltip
│ │ ├── index.js
│ │ ├── style.scss
│ │ ├── test
│ │ │ ├── index.test.js
│ │ │ └── tooltip-base.test.js
│ │ └── tooltip-base.js
│ ├── webhook-description
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ ├── index.js
│ │ └── warning-icon.js
│ └── webhook-information
│ │ ├── __tests__
│ │ └── index.test.js
│ │ └── index.js
├── data
│ ├── account-keys
│ │ ├── __tests__
│ │ │ ├── actions.test.js
│ │ │ ├── hooks.test.js
│ │ │ ├── reducer.test.js
│ │ │ └── selectors.test.js
│ │ ├── action-types.js
│ │ ├── actions.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── resolvers.js
│ │ └── selectors.js
│ ├── account
│ │ ├── __tests__
│ │ │ ├── actions.test.js
│ │ │ ├── hooks.test.js
│ │ │ ├── reducer.test.js
│ │ │ └── selectors.test.js
│ │ ├── action-types.js
│ │ ├── actions.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── resolvers.js
│ │ └── selectors.js
│ ├── constants.js
│ ├── index.js
│ ├── payment-gateway
│ │ ├── __tests__
│ │ │ ├── actions.test.js
│ │ │ ├── hooks.test.js
│ │ │ ├── reducer.test.js
│ │ │ ├── resolvers.test.js
│ │ │ └── selectors.test.js
│ │ ├── action-types.js
│ │ ├── actions.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── resolvers.js
│ │ └── selectors.js
│ ├── settings
│ │ ├── __tests__
│ │ │ ├── actions.js
│ │ │ ├── hooks.js
│ │ │ ├── reducer.js
│ │ │ ├── resolvers.js
│ │ │ └── selectors.js
│ │ ├── action-types.js
│ │ ├── actions.js
│ │ ├── hooks.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── resolvers.js
│ │ └── selectors.js
│ └── store.js
├── entrypoints
│ ├── amazon-pay-settings
│ │ ├── __tests__
│ │ │ ├── amazon-pay-settings-locations.test.js
│ │ │ └── amazon-pay-settings.test.js
│ │ ├── amazon-pay-enable-section.js
│ │ ├── amazon-pay-page.js
│ │ ├── amazon-pay-settings-section.js
│ │ ├── express-checkout-button-preview.js
│ │ └── index.js
│ ├── express-checkout
│ │ ├── index.js
│ │ └── styles.scss
│ ├── payment-gateways
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ ├── disable-confirmation-modal.js
│ │ ├── index.js
│ │ ├── payment-gateways-confirmation.js
│ │ └── style.scss
│ └── payment-request-settings
│ │ ├── __tests__
│ │ ├── payment-request-button-preview.test.js
│ │ ├── payment-request-settings-locations.test.js
│ │ └── payment-request-settings.test.js
│ │ ├── express-checkout-button-preview.js
│ │ ├── index.js
│ │ ├── payment-request-button-preview.js
│ │ ├── payment-request-enable-section.js
│ │ ├── payment-request-page.js
│ │ ├── payment-request-settings-section.js
│ │ ├── style.scss
│ │ └── utils
│ │ └── utils.js
├── express-checkout
│ ├── __tests__
│ │ └── event-handler.test.js
│ ├── compatibility
│ │ ├── __tests__
│ │ │ ├── wc-order-attribution.test.js
│ │ │ └── wc-product-page.test.js
│ │ ├── wc-order-attribution.js
│ │ └── wc-product-page.js
│ ├── event-handler.js
│ ├── payment-flow.js
│ ├── transformers
│ │ ├── __tests__
│ │ │ └── wc-to-stripe.test.js
│ │ └── wc-to-stripe.js
│ └── utils
│ │ ├── __tests__
│ │ ├── index.test.js
│ │ └── normalize.test.js
│ │ ├── check-payment-method-availability.js
│ │ ├── index.js
│ │ └── normalize.js
├── hooks
│ └── use-toggle.js
├── index.js
├── optimized-checkout
│ ├── __tests__
│ │ ├── apply-styles.test.js
│ │ ├── handle-display-of-payment-instructions.test.js
│ │ └── handle-display-of-saving-checkbox.test.js
│ ├── apply-styles.js
│ ├── handle-display-of-payment-instructions.js
│ └── handle-display-of-saving-checkbox.js
├── payment-method-icons
│ ├── affirm
│ │ ├── icon.svg
│ │ └── index.js
│ ├── afterpay
│ │ ├── icon.svg
│ │ └── index.js
│ ├── alipay
│ │ ├── icon.svg
│ │ └── index.js
│ ├── amazon-pay
│ │ ├── icon.svg
│ │ └── index.js
│ ├── apple-pay
│ │ ├── icon-black.svg
│ │ ├── icon-white.svg
│ │ └── index.js
│ ├── bacs-debit
│ │ ├── icon.svg
│ │ └── index.js
│ ├── bancontact
│ │ ├── icon.svg
│ │ └── index.js
│ ├── bank-debit
│ │ ├── icon.svg
│ │ └── index.js
│ ├── blik
│ │ ├── icon.svg
│ │ └── index.js
│ ├── boleto
│ │ ├── icon.svg
│ │ └── index.js
│ ├── cards
│ │ ├── icon.svg
│ │ └── index.js
│ ├── cashapp
│ │ ├── icon.svg
│ │ └── index.js
│ ├── clearpay
│ │ ├── icon.svg
│ │ └── index.js
│ ├── eps
│ │ ├── icon.svg
│ │ └── index.js
│ ├── giropay
│ │ ├── icon.svg
│ │ └── index.js
│ ├── google-pay
│ │ ├── icon-white.svg
│ │ ├── icon.svg
│ │ └── index.js
│ ├── ideal
│ │ ├── icon.svg
│ │ └── index.js
│ ├── index.js
│ ├── klarna
│ │ ├── icon.svg
│ │ └── index.js
│ ├── link
│ │ ├── icon-black.svg
│ │ ├── icon.svg
│ │ └── index.js
│ ├── multibanco
│ │ ├── icon.svg
│ │ └── index.js
│ ├── oxxo
│ │ ├── icon.svg
│ │ └── index.js
│ ├── p24
│ │ ├── icon.svg
│ │ └── index.js
│ ├── payment-request
│ │ ├── icon.svg
│ │ └── index.js
│ ├── sepa
│ │ ├── icon.svg
│ │ └── index.js
│ ├── sofort
│ │ ├── icon.svg
│ │ └── index.js
│ ├── stripe
│ │ ├── icon.svg
│ │ └── index.js
│ ├── styles
│ │ ├── base-icon.js
│ │ └── icon-with-shell.js
│ └── wechat-pay
│ │ ├── icon.svg
│ │ └── index.js
├── payment-methods-map.js
├── settings
│ ├── __tests__
│ │ └── loadable-account-section.test.js
│ ├── account-details
│ │ ├── __tests__
│ │ │ └── account-details.test.js
│ │ ├── index.js
│ │ └── use-webhook-state-message.js
│ ├── advanced-settings-section
│ │ ├── __tests__
│ │ │ ├── index.test.js
│ │ │ └── single-payment-element-feature.test.js
│ │ ├── debug-mode.js
│ │ ├── experimental-features.js
│ │ ├── index.js
│ │ └── single-payment-element-feature.js
│ ├── card-body.js
│ ├── card-footer.js
│ ├── connect-stripe-account
│ │ ├── __tests__
│ │ │ └── connect-stripe-account.test.js
│ │ └── index.js
│ ├── display-order-customization-notice
│ │ ├── __tests__
│ │ │ └── display-order-customization-notice.js
│ │ └── index.js
│ ├── general-settings-section
│ │ ├── __tests__
│ │ │ ├── customize-payment-method.test.js
│ │ │ ├── general-settings-section.test.js
│ │ │ └── remove-method-confirmation-modal.test.js
│ │ ├── customize-payment-method.js
│ │ ├── index.js
│ │ ├── payment-method-checkbox.js
│ │ ├── payment-method-description.js
│ │ ├── payment-method.js
│ │ ├── payment-methods-list.js
│ │ ├── payment-methods-unavailable-list.js
│ │ ├── remove-method-confirmation-modal.js
│ │ ├── section-footer.js
│ │ ├── section-heading.js
│ │ └── styles.scss
│ ├── index.js
│ ├── loadable-account-section.js
│ ├── loadable-payment-gateway-section.js
│ ├── loadable-settings-section.js
│ ├── notices
│ │ └── legacy-experience-transition
│ │ │ ├── __tests__
│ │ │ └── legacy-experience-transition.test.js
│ │ │ └── index.js
│ ├── payment-gateway-manager
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ ├── constants.js
│ │ └── index.js
│ ├── payment-gateway-section
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ └── index.js
│ ├── payment-method-icon
│ │ ├── index.js
│ │ ├── style.scss
│ │ └── test
│ │ │ └── index.js
│ ├── payment-methods
│ │ └── index.js
│ ├── payment-request-section
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ ├── index.js
│ │ └── styles.scss
│ ├── payment-settings
│ │ ├── __tests__
│ │ │ ├── account-details-section.test.js
│ │ │ ├── disconnect-stripe-confirmation-modal.test.js
│ │ │ ├── general-settings-section.test.js
│ │ │ └── test-mode-checkbox.test.js
│ │ ├── account-details-section.js
│ │ ├── account-keys-connection-status.js
│ │ ├── account-keys-modal.js
│ │ ├── constants.js
│ │ ├── disconnect-stripe-confirmation-modal.js
│ │ ├── general-settings-section.js
│ │ ├── index.js
│ │ ├── promotional-banner
│ │ │ ├── __tests__
│ │ │ │ ├── bnpl-promotion-banner.test.js
│ │ │ │ └── index.test.js
│ │ │ ├── banner-layout.js
│ │ │ ├── bnpl-promotion-banner.js
│ │ │ ├── get-promotional-banner-type.js
│ │ │ ├── illustrations
│ │ │ │ ├── bnpl.svg
│ │ │ │ ├── default.svg
│ │ │ │ └── reconnect.svg
│ │ │ ├── index.js
│ │ │ ├── new-checkout-experience-apms-banner.js
│ │ │ ├── new-checkout-experience-banner.js
│ │ │ └── re-connect-account-banner.js
│ │ ├── style.scss
│ │ └── test-mode-checkbox.js
│ ├── payments-and-transactions-section
│ │ ├── __tests__
│ │ │ ├── index.test.js
│ │ │ └── manual-capture-control.test.js
│ │ ├── index.js
│ │ ├── manual-capture-control.js
│ │ ├── statement-preview
│ │ │ ├── icons
│ │ │ │ ├── bank.js
│ │ │ │ ├── cashApp.js
│ │ │ │ └── creditCard.js
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ └── statement-previews-wrapper.js
│ ├── save-payment-gateway-section
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ └── index.js
│ ├── save-settings-section
│ │ ├── __tests__
│ │ │ └── save-settings-section.test.js
│ │ └── index.js
│ ├── section-status
│ │ └── index.js
│ ├── settings-layout.js
│ ├── settings-manager
│ │ ├── __tests__
│ │ │ └── index.test.js
│ │ └── index.js
│ ├── settings-section
│ │ ├── __tests__
│ │ │ └── settings-section.test.js
│ │ └── index.js
│ ├── stripe-auth-account
│ │ ├── account-status-panel.js
│ │ ├── configure-webhook-button.js
│ │ ├── index.js
│ │ ├── stripe-auth-actions.js
│ │ ├── stripe-auth-diagram.js
│ │ ├── styles.scss
│ │ └── webhook-help-text.js
│ ├── styles.scss
│ └── upe-toggle
│ │ ├── __tests__
│ │ └── provider.test.js
│ │ ├── context.js
│ │ └── provider.js
├── stripe-utils
│ ├── __tests__
│ │ └── cash-app-limit-notice-handler.test.js
│ ├── cash-app-limit-notice-handler.js
│ ├── constants.js
│ ├── index.js
│ ├── type-defs.js
│ └── utils.js
├── styles
│ ├── abstracts
│ │ ├── _breakpoints.scss
│ │ ├── _colors.scss
│ │ ├── _mixins.scss
│ │ ├── _variables.scss
│ │ └── styles.scss
│ └── upe
│ │ ├── __tests__
│ │ ├── index.js
│ │ └── utils.js
│ │ ├── index.js
│ │ ├── upe-styles.js
│ │ └── utils.js
├── tracking
│ ├── __tests__
│ │ └── index.test.js
│ └── index.js
└── utils
│ ├── index.js
│ ├── use-confirm-navigation.js
│ └── use-payment-method-currencies.js
├── composer.lock
├── includes
├── abstracts
│ ├── abstract-wc-stripe-connect-rest-controller.php
│ ├── abstract-wc-stripe-payment-gateway-voucher.php
│ └── abstract-wc-stripe-payment-gateway.php
├── admin
│ ├── class-wc-rest-stripe-account-controller.php
│ ├── class-wc-rest-stripe-account-keys-controller.php
│ ├── class-wc-rest-stripe-connection-tokens-controller.php
│ ├── class-wc-rest-stripe-locations-controller.php
│ ├── class-wc-rest-stripe-orders-controller.php
│ ├── class-wc-rest-stripe-settings-controller.php
│ ├── class-wc-rest-stripe-tokens-controller.php
│ ├── class-wc-stripe-admin-notices.php
│ ├── class-wc-stripe-amazon-pay-controller.php
│ ├── class-wc-stripe-inbox-notes.php
│ ├── class-wc-stripe-payment-gateways-controller.php
│ ├── class-wc-stripe-payment-requests-controller.php
│ ├── class-wc-stripe-privacy.php
│ ├── class-wc-stripe-rest-base-controller.php
│ ├── class-wc-stripe-rest-upe-flag-toggle-controller.php
│ ├── class-wc-stripe-settings-controller.php
│ ├── class-wc-stripe-upe-compatibility-controller.php
│ ├── stripe-alipay-settings.php
│ ├── stripe-bancontact-settings.php
│ ├── stripe-boleto-settings.php
│ ├── stripe-eps-settings.php
│ ├── stripe-giropay-settings.php
│ ├── stripe-ideal-settings.php
│ ├── stripe-multibanco-settings.php
│ ├── stripe-oxxo-settings.php
│ ├── stripe-p24-settings.php
│ ├── stripe-sepa-settings.php
│ ├── stripe-settings.php
│ └── stripe-sofort-settings.php
├── class-wc-gateway-stripe.php
├── class-wc-stripe-account.php
├── class-wc-stripe-action-scheduler-service.php
├── class-wc-stripe-api.php
├── class-wc-stripe-apple-pay-registration.php
├── class-wc-stripe-blocks-support.php
├── class-wc-stripe-co-branded-cc-compatibility.php
├── class-wc-stripe-customer.php
├── class-wc-stripe-database-cache.php
├── class-wc-stripe-exception.php
├── class-wc-stripe-feature-flags.php
├── class-wc-stripe-helper.php
├── class-wc-stripe-intent-controller.php
├── class-wc-stripe-logger.php
├── class-wc-stripe-mode.php
├── class-wc-stripe-order-handler.php
├── class-wc-stripe-order.php
├── class-wc-stripe-payment-method-configurations.php
├── class-wc-stripe-status.php
├── class-wc-stripe-upe-compatibility.php
├── class-wc-stripe-webhook-handler.php
├── class-wc-stripe-webhook-state.php
├── class-wc-stripe.php
├── compat
│ ├── class-wc-stripe-email-failed-authentication-retry.php
│ ├── class-wc-stripe-email-failed-authentication.php
│ ├── class-wc-stripe-email-failed-preorder-authentication.php
│ ├── class-wc-stripe-email-failed-renewal-authentication.php
│ ├── class-wc-stripe-subscriptions-helper.php
│ ├── class-wc-stripe-subscriptions-legacy-sepa-token-update.php
│ ├── class-wc-stripe-woo-compat-utils.php
│ ├── trait-wc-stripe-pre-orders.php
│ ├── trait-wc-stripe-subscriptions-utilities.php
│ └── trait-wc-stripe-subscriptions.php
├── connect
│ ├── class-wc-stripe-connect-api.php
│ ├── class-wc-stripe-connect-rest-oauth-connect-controller.php
│ ├── class-wc-stripe-connect-rest-oauth-init-controller.php
│ └── class-wc-stripe-connect.php
├── constants
│ ├── class-wc-stripe-currency-code.php
│ ├── class-wc-stripe-hong-kong-states.php
│ ├── class-wc-stripe-intent-status.php
│ ├── class-wc-stripe-payment-methods.php
│ └── class-wc-stripe-payment-request-button-states.php
├── deprecated
│ └── class-wc-stripe-apple-pay.php
├── migrations
│ ├── class-allowed-payment-request-button-types-update.php
│ ├── class-migrate-payment-request-data-to-express-checkout-data.php
│ └── class-wc-stripe-subscriptions-repairer-legacy-sepa-tokens.php
├── notes
│ ├── class-wc-stripe-upe-availability-note.php
│ └── class-wc-stripe-upe-stripelink-note.php
├── payment-methods
│ ├── class-wc-gateway-stripe-alipay.php
│ ├── class-wc-gateway-stripe-bancontact.php
│ ├── class-wc-gateway-stripe-boleto.php
│ ├── class-wc-gateway-stripe-eps.php
│ ├── class-wc-gateway-stripe-giropay.php
│ ├── class-wc-gateway-stripe-ideal.php
│ ├── class-wc-gateway-stripe-multibanco.php
│ ├── class-wc-gateway-stripe-oxxo.php
│ ├── class-wc-gateway-stripe-p24.php
│ ├── class-wc-gateway-stripe-sepa.php
│ ├── class-wc-gateway-stripe-sofort.php
│ ├── class-wc-stripe-express-checkout-ajax-handler.php
│ ├── class-wc-stripe-express-checkout-element.php
│ ├── class-wc-stripe-express-checkout-helper.php
│ ├── class-wc-stripe-payment-request.php
│ ├── class-wc-stripe-upe-payment-gateway.php
│ ├── class-wc-stripe-upe-payment-method-ach.php
│ ├── class-wc-stripe-upe-payment-method-acss.php
│ ├── class-wc-stripe-upe-payment-method-affirm.php
│ ├── class-wc-stripe-upe-payment-method-afterpay-clearpay.php
│ ├── class-wc-stripe-upe-payment-method-alipay.php
│ ├── class-wc-stripe-upe-payment-method-amazon-pay.php
│ ├── class-wc-stripe-upe-payment-method-bacs-debit.php
│ ├── class-wc-stripe-upe-payment-method-bancontact.php
│ ├── class-wc-stripe-upe-payment-method-becs-debit.php
│ ├── class-wc-stripe-upe-payment-method-blik.php
│ ├── class-wc-stripe-upe-payment-method-boleto.php
│ ├── class-wc-stripe-upe-payment-method-cash-app-pay.php
│ ├── class-wc-stripe-upe-payment-method-cc.php
│ ├── class-wc-stripe-upe-payment-method-eps.php
│ ├── class-wc-stripe-upe-payment-method-giropay.php
│ ├── class-wc-stripe-upe-payment-method-ideal.php
│ ├── class-wc-stripe-upe-payment-method-klarna.php
│ ├── class-wc-stripe-upe-payment-method-link.php
│ ├── class-wc-stripe-upe-payment-method-multibanco.php
│ ├── class-wc-stripe-upe-payment-method-oxxo.php
│ ├── class-wc-stripe-upe-payment-method-p24.php
│ ├── class-wc-stripe-upe-payment-method-sepa.php
│ ├── class-wc-stripe-upe-payment-method-sofort.php
│ ├── class-wc-stripe-upe-payment-method-wechat-pay.php
│ └── class-wc-stripe-upe-payment-method.php
└── payment-tokens
│ ├── class-wc-stripe-ach-payment-token.php
│ ├── class-wc-stripe-acss-payment-token.php
│ ├── class-wc-stripe-amazon-pay-payment-token.php
│ ├── class-wc-stripe-bacs-payment-token.php
│ ├── class-wc-stripe-becs-debit-payment-token.php
│ ├── class-wc-stripe-cash-app-payment-token.php
│ ├── class-wc-stripe-cc-payment-token.php
│ ├── class-wc-stripe-link-payment-token.php
│ ├── class-wc-stripe-payment-tokens.php
│ ├── class-wc-stripe-sepa-payment-token.php
│ ├── interface-wc-stripe-payment-method-comparison.php
│ └── trait-wc-stripe-fingerprint.php
├── languages
└── woocommerce-gateway-stripe.pot
├── lint-staged.config.js
├── phpcs.xml.dist
├── readme.txt
├── tasks
└── release.js
├── templates
└── emails
│ ├── failed-preorder-authentication.php
│ ├── failed-renewal-authentication-requested.php
│ ├── failed-renewal-authentication.php
│ └── plain
│ ├── failed-preorder-authentication.php
│ ├── failed-renewal-authentication-requested.php
│ └── failed-renewal-authentication.php
├── uninstall.php
└── woocommerce-gateway-stripe.php
/.husky/post-checkout:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed
5 | npx --no-install yarnhook
6 |
7 | # make sure the autoload files are regenerated
8 | composer dumpautoload
9 |
--------------------------------------------------------------------------------
/.husky/post-merge:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed
5 | npx --no-install yarnhook
6 |
7 | # make sure the autoload files are regenerated
8 | composer dumpautoload
9 |
10 | DEV_TOOLS_PLUGIN_PATH=${LOCAL_STRIPE_DEV_TOOLS_PLUGIN_REPO_PATH:-"docker/wordpress/wp-content/plugins/woocommerce-gateway-stripe-dev-tools"}
11 | if [ ! -d $DEV_TOOLS_PLUGIN_PATH ]; then
12 | echo
13 | echo "\033[33mCouldn't find the '$DEV_TOOLS_PLUGIN_PATH' directory. Skipping the auto-update for the Stripe Dev Tools plugin...\033[0m"
14 | else
15 | if [ "$(cd $DEV_TOOLS_PLUGIN_PATH && git rev-parse --show-toplevel 2>/dev/null)" = "$(cd $DEV_TOOLS_PLUGIN_PATH && pwd)" ]; then
16 | echo
17 | echo "\033[32mDetermining if there is an update for the Stripe Dev Tools plugin...\033[0m"
18 |
19 | DEV_TOOLS_BRANCH=$(cd $DEV_TOOLS_PLUGIN_PATH && git branch --show-current)
20 | if [[ $DEV_TOOLS_BRANCH = "trunk" ]]; then
21 | echo " \033[32mThe current branch is $DEV_TOOLS_BRANCH. Check if we are safe to pull from origin/$DEV_TOOLS_BRANCH...\033[0m"
22 | if [[ `cd $DEV_TOOLS_PLUGIN_PATH && git status --porcelain` ]]; then
23 | echo "\033[33m There are uncommitted local changes on the Stripe Dev Tools repo. Skipping any attempt to update it.\033[0m"
24 | else
25 | echo " \033[32mPulling the latest changes from origin/$DEV_TOOLS_BRANCH, if any...\033[0m"
26 | cd $DEV_TOOLS_PLUGIN_PATH && git pull
27 | fi
28 | else
29 | echo "\033[33m The Stripe Dev Tools local clone is not on the trunk branch. Skipping any attempt to update it.\033[0m"
30 | fi
31 | fi
32 | fi
--------------------------------------------------------------------------------
/.husky/post-rewrite:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # using `--no-install` just in case it's the first time a person is checking out the repo and doesn't have yarnhook installed
5 | npx --no-install yarnhook
6 |
7 | # make sure the autoload files are regenerated
8 | composer dumpautoload
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | RED="\033[1;31m"
5 | NO_COLOR="\033[0m"
6 |
7 | BRANCH=`git rev-parse --abbrev-ref HEAD`
8 | PROTECTED_BRANCHES="^(trunk|develop|release/*)"
9 |
10 | # Only show warning for 'develop', 'trunk' and 'release/...' branches.
11 | if ! [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]]; then
12 | exit 0
13 | fi
14 |
15 | # Ask for confirmation, anything other than 'y' or 'Y' is considered as a NO.
16 | echo "\nYou're about to push to ${RED}${BRANCH}${NO_COLOR} 😱, is that what you intended? [y|n] \c"
17 | read -n 1 -r < /dev/tty
18 | echo "\n"
19 | if echo $REPLY | grep -E '^[Yy]$' > /dev/null; then
20 | exit 0
21 | fi
22 |
23 | exit 1
24 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
2 | engine-strict=true
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.18.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | bin
2 | build
3 | assets/js
4 | dist
5 | docker
6 | node_modules
7 | release
8 | vendor
9 | wordpress_org_assets
10 |
11 | tests/e2e/docker
12 | tests/e2e/deps
13 | tests/e2e/output
14 | tests/e2e/report
15 | tests/e2e/allure-results
16 | tests/e2e/storage
17 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | assets/css/stripe-styles.css
2 | assets/css/stripe-styles.scss
3 | assets/css/stripe-link.css
4 | assets/css/stripe-link.scss
5 | assets/css/payment-methods-styles.css
6 | assets/css/payment-methods-styles.scss
7 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "stylelint-config-wordpress", "stylelint-config-prettier" ],
3 | "syntax": "scss",
4 | "rules": {
5 | "at-rule-empty-line-before": null,
6 | "at-rule-no-unknown": null,
7 | "comment-empty-line-before": null,
8 | "declaration-block-no-duplicate-properties": null,
9 | "declaration-colon-newline-after": null,
10 | "declaration-property-unit-whitelist": null,
11 | "font-family-name-quotes": "always-unless-keyword",
12 | "font-family-no-missing-generic-family-keyword": null,
13 | "font-weight-notation": null,
14 | "function-url-quotes": "always",
15 | "max-line-length": null,
16 | "media-feature-parentheses-space-inside": "always",
17 | "no-descending-specificity": null,
18 | "no-duplicate-selectors": null,
19 | "rule-empty-line-before": null,
20 | "selector-class-pattern": null,
21 | "selector-pseudo-class-parentheses-space-inside": "always",
22 | "string-quotes": "single",
23 | "value-keyword-case": null,
24 | "value-list-comma-newline-after": null
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Listen for Xdebug (remote/docker)",
9 | "type": "php",
10 | "request": "launch",
11 | "hostname": "0.0.0.0",
12 | "port": 9000,
13 | "pathMappings": {
14 | "/var/www/html/": "${workspaceFolder}/docker/wordpress",
15 | "/var/www/html/wp-content/plugins/woocommerce-gateway-stripe/": "${workspaceFolder}"
16 | },
17 | "preLaunchTask": "enable:xdebug",
18 | "postDebugTask": "disable:xdebug"
19 | },
20 | {
21 | "name": "Listen for Xdebug (local)",
22 | "type": "php",
23 | "request": "launch",
24 | "port": 9000,
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "shell",
8 | "command": "npm run xdebug:start",
9 | "label": "enable:xdebug"
10 | },
11 | {
12 | "type": "shell",
13 | "command": "npm run xdebug:stop",
14 | "label": "disable:xdebug"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/assets/css/stripe-link.css:
--------------------------------------------------------------------------------
1 | .stripe-gateway-checkout-email-field{position:relative}.stripe-gateway-checkout-email-field button.stripe-gateway-stripelink-modal-trigger{display:none;position:absolute;right:5px;width:64px;height:40px;background:no-repeat url(../images/link.svg);background-color:transparent;cursor:pointer;border:none}
--------------------------------------------------------------------------------
/assets/css/stripe-link.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sourceRoot":"","sources":["stripe-link.scss"],"names":[],"mappings":"AAAA,qCACC,kBAGD,oFACC,aACA,kBACA,UACA,WACA,YACA,+CAEA,6BACA,eACA","file":"stripe-link.css"}
--------------------------------------------------------------------------------
/assets/css/stripe-styles.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sourceRoot":"","sources":["stripe-styles.scss"],"names":[],"mappings":"AACA,wDAEC,sBACA,aACA,YACA,sBACA,UAED,iEACC,eAED,+DACC,eAGD,wIAEC,eACA,iBACA,SAGD,8JAEC,gBACA,eAGD,oJAEC,gBAGD,gJAEC,eAGD,sJAEC,eAGD,sJAEC,eAGD,kJAEC,eAGD,8JAEC,gBAGD,gJAEC,gBAGD,wJAEC,eAGD,4JAEC,kBACA,QACA,iBACA,WACA,sDACA,cACA,WACA,YAGD,8IAEC,kBACA,QACA,iBACA,WACA,+CACA,cACA,WACA,YAGD,8IAEC,kBACA,QACA,iBACA,WACA,+CACA,cACA,WACA,YAGD,kJAEC,kBACA,QACA,iBACA,WACA,iDACA,cACA,WACA,YAGD,sJAEC,kBACA,QACA,iBACA,WACA,mDACA,cACA,WACA,YAGD,4IAEC,kBACA,QACA,iBACA,WACA,8CACA,cACA,WACA,YAGD,oJAEC,kBACA,QACA,iBACA,WACA,kDACA,cACA,WACA,YAGD,0JAEC,kBACA,QACA,iBACA,WACA,qDACA,cACA,WACA,YAGD,wIAEC,kBAGD,gFAGC,eACA,kBAGD,yBACC,cACA,WAGD,qDACC,WAGD,aACC,8BACA,kCACA,4BACA,wBACA,SACA,kBACA,eACA,YACA,gBACA,kBACA,WAEA,mBAEC,uBACA,sBAGA,mBAEA,0BACC,sBAED,yBACC,yBAED,yBACC,iDACA,UAIF,2BAEC,uBACA,sBAGA,4EAIA,mBAEA,kCACC,sBAED,iCACC,yBAED,iCACC,iDACA,UAIF,kBACC,sBACA,uBACA,yBACC,yBAED,wBACC,yBAED,wBACC,iDACA,UAKD,0DAEC,4EAED,wBACC","file":"stripe-styles.css"}
--------------------------------------------------------------------------------
/assets/images/affirm.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/images/afterpay.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/alipay.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/bancontact.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/assets/images/bank-debit.svg:
--------------------------------------------------------------------------------
1 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/images/boleto.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/cards.svg:
--------------------------------------------------------------------------------
1 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/images/cashapp.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/credit-card.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/diners.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/discover.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/giropay.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/images/ideal.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/assets/images/jcb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/klarna.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/link.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/assets/images/mastercard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/stripe.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/images/visa.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/wechat.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // Note it is important to have file named babel.config.js because tests fail if named .babelrc
2 | // :exploding_head: same case here: https://github.com/facebook/jest/issues/9292#issuecomment-625750534
3 | module.exports = {
4 | ignore: [],
5 | presets: [ '@wordpress/babel-preset-default' ],
6 | plugins: [
7 | '@emotion',
8 | [ '@babel/transform-runtime', { corejs: 3 } ],
9 | '@babel/plugin-proposal-optional-chaining',
10 | '@babel/plugin-proposal-nullish-coalescing-operator',
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/client/blocks/__tests__/utils.test.js:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import {
3 | extractOrderAttributionData,
4 | populateOrderAttributionInputs,
5 | } from 'wcstripe/blocks/utils';
6 |
7 | describe( 'Blocks Utils', () => {
8 | describe( 'extractOrderAttributionData', () => {
9 | it( 'order attribution wrapper not found', () => {
10 | const data = extractOrderAttributionData();
11 | expect( data ).toStrictEqual( {} );
12 | } );
13 |
14 | it( 'order attribution wrapper exists', () => {
15 | render(
16 |
17 |
18 |
19 |
20 | );
21 |
22 | const data = extractOrderAttributionData();
23 | expect( data ).toStrictEqual( {
24 | foo: 'bar',
25 | baz: 'qux',
26 | } );
27 | } );
28 | } );
29 |
30 | describe( 'populateOrderAttributionInputs', () => {
31 | test( 'order attribution global present', () => {
32 | global.wc_order_attribution = {
33 | params: {
34 | allowTracking: true,
35 | },
36 | setOrderTracking: jest.fn(),
37 | };
38 |
39 | populateOrderAttributionInputs();
40 |
41 | expect(
42 | global.wc_order_attribution.setOrderTracking
43 | ).toHaveBeenCalledWith( true );
44 | } );
45 | } );
46 | } );
47 |
--------------------------------------------------------------------------------
/client/blocks/credit-card/constants.js:
--------------------------------------------------------------------------------
1 | export const PAYMENT_METHOD_NAME = 'stripe';
2 | export const WC_STORE_CART = 'wc/store/cart';
3 |
--------------------------------------------------------------------------------
/client/blocks/express-checkout/constants.js:
--------------------------------------------------------------------------------
1 | export const PAYMENT_METHOD_EXPRESS_CHECKOUT_ELEMENT =
2 | 'express_checkout_element';
3 |
--------------------------------------------------------------------------------
/client/blocks/express-checkout/express-button-previews/style.scss:
--------------------------------------------------------------------------------
1 | .wc-stripe-payment-button-preview {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | background-color: #000;
6 | border-radius: 5px;
7 | height: 40px;
8 | img {
9 | height: 22px;
10 | }
11 | &:hover {
12 | cursor: pointer;
13 | filter: opacity(0.7);
14 | }
15 | /* Amazon Pay Overrides */
16 | &.wc-stripe-amazon-pay-preview {
17 | background-color: #ffd814;
18 | img {
19 | height: 40px;
20 | }
21 | }
22 | /* Stripe Link Overrides */
23 | &.wc-stripe-link-preview {
24 | background-color: #00d66f;
25 | img {
26 | height: 40px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/client/blocks/express-checkout/express-checkout-container.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Elements } from '@stripe/react-stripe-js';
3 | import ExpressCheckoutComponent from './express-checkout-component';
4 | import {
5 | getExpressCheckoutButtonAppearance,
6 | getExpressCheckoutData,
7 | getPaymentMethodTypesForExpressMethod,
8 | isManualPaymentMethodCreation,
9 | } from 'wcstripe/express-checkout/utils';
10 |
11 | export const ExpressCheckoutContainer = ( props ) => {
12 | const { stripe, billing, expressPaymentMethod } = props;
13 | const options = {
14 | mode: 'payment',
15 | ...( isManualPaymentMethodCreation( expressPaymentMethod ) && {
16 | paymentMethodCreation: 'manual',
17 | } ),
18 | amount: billing.cartTotal.value,
19 | currency: billing.currency.code.toLowerCase(),
20 | paymentMethodTypes: getPaymentMethodTypesForExpressMethod(
21 | expressPaymentMethod
22 | ),
23 | appearance: getExpressCheckoutButtonAppearance(),
24 | locale: getExpressCheckoutData( 'stripe' )?.locale ?? 'en',
25 | };
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/client/blocks/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | registerPaymentMethod,
3 | registerExpressPaymentMethod,
4 | } from '@woocommerce/blocks-registry';
5 | import stripeCcPaymentMethod from './credit-card';
6 | import paymentRequestPaymentMethod from './payment-request';
7 |
8 | // Register Stripe Credit Card.
9 | registerPaymentMethod( stripeCcPaymentMethod );
10 |
11 | // Register Stripe Payment Request.
12 | registerExpressPaymentMethod( paymentRequestPaymentMethod );
13 |
--------------------------------------------------------------------------------
/client/blocks/load-stripe.js:
--------------------------------------------------------------------------------
1 | import { loadStripe } from '@stripe/stripe-js';
2 | import { getApiKey, getBlocksConfiguration } from './utils';
3 |
4 | const stripePromise = () =>
5 | new Promise( ( resolve ) => {
6 | try {
7 | // Default to the 'auto' locale so Stripe chooses the browser's locale
8 | // if the store's locale is not available.
9 | const locale = getBlocksConfiguration()?.stripe_locale ?? 'auto';
10 | resolve( loadStripe( getApiKey(), { locale } ) );
11 | } catch ( error ) {
12 | // In order to avoid showing console error publicly to users,
13 | // we resolve instead of rejecting when there is an error.
14 | resolve( { error } );
15 | }
16 | } );
17 |
18 | export { stripePromise as loadStripe };
19 |
--------------------------------------------------------------------------------
/client/blocks/payment-request/constants.js:
--------------------------------------------------------------------------------
1 | export const PAYMENT_METHOD_NAME = 'payment_request';
2 |
--------------------------------------------------------------------------------
/client/blocks/payment-request/custom-button.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import { getBlocksConfiguration } from 'wcstripe/blocks/utils';
3 |
4 | export const CustomButton = ( { onButtonClicked } ) => {
5 | const {
6 | theme = 'dark',
7 | height = '44',
8 | customLabel = __( 'Buy now', 'woocommerce-gateway-stripe' ),
9 | } = getBlocksConfiguration()?.button;
10 | return (
11 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/client/blocks/payment-request/login-confirmation.js:
--------------------------------------------------------------------------------
1 | import { getBlocksConfiguration } from 'wcstripe/blocks/utils';
2 |
3 | /**
4 | * Displays a `confirm` dialog which leads to a redirect.
5 | *
6 | * @param {string} paymentRequestType Can be either apple_pay, google_pay or payment_request_api.
7 | */
8 | export const displayLoginConfirmation = ( paymentRequestType ) => {
9 | if ( ! getBlocksConfiguration()?.login_confirmation ) {
10 | return;
11 | }
12 |
13 | let message = getBlocksConfiguration()?.login_confirmation?.message;
14 |
15 | // Replace dialog text with specific payment request type "Apple Pay" or "Google Pay".
16 | if ( paymentRequestType !== 'payment_request_api' ) {
17 | message = message.replace(
18 | /\*\*.*?\*\*/,
19 | paymentRequestType === 'apple_pay' ? 'Apple Pay' : 'Google Pay'
20 | );
21 | }
22 |
23 | // Remove asterisks from string.
24 | message = message.replace( /\*\*/g, '' );
25 |
26 | // eslint-disable-next-line no-alert, no-undef
27 | if ( confirm( message ) ) {
28 | // Redirect to my account page.
29 | window.location.href = getBlocksConfiguration()?.login_confirmation?.redirect_url;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/client/blocks/three-d-secure/index.js:
--------------------------------------------------------------------------------
1 | export * from './three-d-secure-payment-handler';
2 | export * from './use-payment-intents';
3 |
--------------------------------------------------------------------------------
/client/blocks/three-d-secure/three-d-secure-payment-handler.js:
--------------------------------------------------------------------------------
1 | import { Elements, useStripe } from '@stripe/react-stripe-js';
2 | import { usePaymentIntents } from './use-payment-intents';
3 |
4 | const sourceIdNoop = () => void null;
5 |
6 | const Handler = ( { eventRegistration, emitResponse } ) => {
7 | const stripe = useStripe();
8 | const { onCheckoutAfterProcessingWithSuccess } = eventRegistration;
9 | usePaymentIntents(
10 | stripe,
11 | onCheckoutAfterProcessingWithSuccess,
12 | sourceIdNoop,
13 | emitResponse
14 | );
15 | return null;
16 | };
17 |
18 | export const ThreeDSecurePaymentHandler = ( { stripe, ...props } ) => {
19 | return (
20 |
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/client/blocks/upe/call-when-element-is-available.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Call a function when an element is available in the DOM.
3 | *
4 | * @param {string} selector The selector to look for.
5 | * @param {Function} callable fThe function to call when the element is available.
6 | * @param {Array} params The parameters to pass to the callable function.
7 | */
8 | export function callWhenElementIsAvailable( selector, callable, params = [] ) {
9 | const checkoutBlock = document.querySelector(
10 | '[data-block-name="woocommerce/checkout"]'
11 | );
12 |
13 | if ( ! checkoutBlock ) {
14 | return;
15 | }
16 |
17 | const observer = new MutationObserver( ( mutationList, obs ) => {
18 | if ( document.querySelector( selector ) ) {
19 | // Element found, run the function and disconnect the observer.
20 | callable( ...params );
21 | obs.disconnect();
22 | }
23 | } );
24 |
25 | observer.observe( checkoutBlock, {
26 | childList: true,
27 | subtree: true,
28 | } );
29 | }
30 |
--------------------------------------------------------------------------------
/client/blocks/upe/confirm-card-payment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Handles the confirmation of card payments (3DSv2 modals/SCA challenge).
3 | *
4 | * @param {Object} api The API used for connection both with the server and Stripe.
5 | * @param {Object} paymentDetails Details about the payment, received from the server.
6 | * @param {Object} emitResponse Various helpers for usage with observer response objects.
7 | * @param {boolean} shouldSavePayment Indicates whether the payment method should be saved or not.
8 | * @return {Object} An object, which contains the result from the action.
9 | */
10 | export default async function confirmCardPayment(
11 | api,
12 | paymentDetails,
13 | emitResponse,
14 | shouldSavePayment
15 | ) {
16 | const { redirect, payment_method: paymentMethod } = paymentDetails;
17 |
18 | try {
19 | const confirmation = api.confirmIntent(
20 | redirect,
21 | shouldSavePayment ? paymentMethod : null
22 | );
23 |
24 | // `true` means there is no intent to confirm.
25 | if ( confirmation === true ) {
26 | return {
27 | type: 'success',
28 | redirectUrl: redirect,
29 | };
30 | }
31 |
32 | // `confirmIntent` also returns `isOrderPage`, but that's not supported in blocks yet.
33 | const { request } = confirmation;
34 |
35 | const finalRedirect = await request;
36 | return {
37 | type: 'success',
38 | redirectUrl: finalRedirect,
39 | };
40 | } catch ( error ) {
41 | return {
42 | type: 'error',
43 | message: error.message,
44 | messageContext: emitResponse.noticeContexts.PAYMENTS,
45 | };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/client/blocks/upe/saved-token-handler.js:
--------------------------------------------------------------------------------
1 | import { usePaymentCompleteHandler } from './hooks';
2 |
3 | export const SavedTokenHandler = ( {
4 | api,
5 | stripe,
6 | elements,
7 | eventRegistration: { onCheckoutAfterProcessingWithSuccess },
8 | emitResponse,
9 | } ) => {
10 | // Once the server has completed payment processing, confirm the intent of necessary.
11 | usePaymentCompleteHandler(
12 | api,
13 | stripe,
14 | elements,
15 | onCheckoutAfterProcessingWithSuccess,
16 | emitResponse,
17 | false // No need to save a payment that has already been saved.
18 | );
19 |
20 | return <>>;
21 | };
22 |
--------------------------------------------------------------------------------
/client/blocks/upe/styles.scss:
--------------------------------------------------------------------------------
1 | button.stripe-gateway-stripelink-modal-trigger {
2 | display: none;
3 | position: absolute;
4 | right: 5px;
5 | width: 64px;
6 | height: 40px;
7 | background: no-repeat
8 | url( '../../payment-method-icons/link/icon.svg' );
9 | background-color: transparent !important;
10 | cursor: pointer;
11 | border: none;
12 | }
13 | button.stripe-gateway-stripelink-modal-trigger:hover {
14 | background-color: transparent;
15 | border-color: transparent;
16 | }
17 | .wc-block-checkout__payment-method .wc-block-components-radio-control__label > span {
18 | width: 100%;
19 | }
20 | .wc-block-checkout__payment-method .wc-block-components-radio-control__label > span > span {
21 | float:right;
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | }
26 | /* stylelint-disable selector-id-pattern */
27 | #radio-control-wc-payment-method-options-stripe__content.single-payment-element {
28 | padding-top: 1.4em;
29 |
30 | .content {
31 | display: none;
32 | }
33 | }
34 | #radio-control-wc-payment-method-options-stripe__label > span > span {
35 | border: none;
36 | }
37 | /* stylelint-enable selector-id-pattern */
38 |
--------------------------------------------------------------------------------
/client/blocks/upe/upe-deferred-intent-creation/blik-code-element.js:
--------------------------------------------------------------------------------
1 | import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
2 | import { __ } from '@wordpress/i18n';
3 | import { useState } from 'react';
4 |
5 | const BlikCodeElement = () => {
6 | const [ blikCode, setBlikCode ] = useState( '' );
7 |
8 | return (
9 | <>
10 | {
18 | if ( validity.valueMissing ) {
19 | return __(
20 | 'Please enter a valid BLIK code',
21 | 'woocommerce-gateway-stripe'
22 | );
23 | }
24 |
25 | if ( validity.patternMismatch ) {
26 | return __(
27 | 'BLIK Code is invalid',
28 | 'woocommerce-gateway-stripe'
29 | );
30 | }
31 | } }
32 | required
33 | />
34 |
39 | { __(
40 | 'After submitting your order, please authorize the payment in your mobile banking application.',
41 | 'woocommerce-gateway-stripe'
42 | ) }
43 |
44 | >
45 | );
46 | };
47 |
48 | export default BlikCodeElement;
49 |
--------------------------------------------------------------------------------
/client/brand-logos/stripe-mark/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import classNames from 'classnames';
3 | import mark from './mark.svg';
4 |
5 | /**
6 | * StripeMark component.
7 | *
8 | * @param {Object} props The component props.
9 | * @param {string} [props.className] Additional classes to add to the StripeMark component.
10 | * @param {any} props.restProps Additional props to add to the StripeMark component.
11 | *
12 | * @return {JSX.Element} The rendered StripeMark component.
13 | */
14 | const StripeMark = ( { className, ...restProps } ) => (
15 |
23 | );
24 |
25 | export default StripeMark;
26 |
--------------------------------------------------------------------------------
/client/brand-logos/stripe-mark/mark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/brand-logos/woo-white/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import classNames from 'classnames';
3 | import logo from './logo.svg';
4 |
5 | /**
6 | * WooLogo component.
7 | *
8 | * @param {Object} props The component props.
9 | * @param {string} [props.className] Additional classes to add to the WooLogo component.
10 | * @param {any} props.restProps Additional props to add to the WooLogo component.
11 | *
12 | * @return {JSX.Element} The rendered WooLogo component.
13 | */
14 | const WooLogo = ( { className, ...restProps } ) => (
15 |
26 | );
27 |
28 | export default WooLogo;
29 |
--------------------------------------------------------------------------------
/client/brand-logos/woo-white/logo.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/client/classic/upe/style.scss:
--------------------------------------------------------------------------------
1 | .wc-stripe-upe-element {
2 | margin-bottom: 4px;
3 | }
4 |
5 | #payment .payment_methods li {
6 | img.stripe-icon {
7 | height: 24px;
8 | width: 37px;
9 | object-fit: contain;
10 | }
11 | img.stripe-boleto-icon,
12 | img.stripe-sepa-icon {
13 | padding: 4px;
14 | }
15 | img.stripe-sepa-icon {
16 | background: #10298e;
17 | }
18 | }
19 |
20 | .woocommerce-checkout #payment ul.payment_methods li img.stripe-multibanco-icon {
21 | max-height: 30px;
22 | }
23 |
24 | .woocommerce-checkout #payment ul.payment_methods li img.stripe-alipay-icon {
25 | max-width: 50px;
26 | }
27 |
--------------------------------------------------------------------------------
/client/components/chip/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import './styles.scss';
4 |
5 | /**
6 | * The Chip component.
7 | *
8 | * The Chip component is used to display a small piece of information in a colored chip.
9 | * It implements the 'Chip' element described in the WooCommerce Design Library.
10 | *
11 | * @param {Object} props The component props.
12 | * @param {string} props.text The text of the chip.
13 | * @param {string} props.color The color of the chip. Can be 'gray', 'green', 'blue', 'red', 'yellow'.
14 | * @param {JSX.Element} props.icon Optional icon for the chip.
15 | * @param {string} props.iconPosition The position of the icon. Default is 'right'. Can be 'left' or 'right'.
16 | *
17 | * @return {JSX.Element} The rendered Chip component.
18 | */
19 | const Chip = ( { text, color = 'gray', icon, iconPosition = 'right' } ) => {
20 | return (
21 |
27 | { iconPosition === 'left' && icon && <>{ icon }> }
28 | { text }
29 | { iconPosition === 'right' && icon && <>{ icon }> }
30 |
31 | );
32 | };
33 |
34 | export default Chip;
35 |
--------------------------------------------------------------------------------
/client/components/chip/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .wcstripe-chip {
4 | font-size: 12px;
5 | display: inline-flex;
6 | align-items: center;
7 | justify-content: center;
8 | font-weight: 400;
9 | padding: 0.25rem 0.5rem;
10 | border-radius: 0.2rem;
11 | line-height: 16px;
12 | white-space: nowrap;
13 | flex-direction: row;
14 | gap: 2px;
15 |
16 | &.wc-stripe-chip-gray {
17 | background: $wp-gray-0;
18 | color: $wp-gray-80;
19 | svg {
20 | fill: $wp-gray-80;
21 | }
22 | }
23 | &.wc-stripe-chip-green {
24 | background: $wp-green-0;
25 | color: $wp-green-70;
26 | svg {
27 | fill: $wp-green-70;
28 | }
29 | }
30 | &.wc-stripe-chip-blue {
31 | background: $wp-blue-0;
32 | color: $wp-blue-70;
33 | svg {
34 | fill: $wp-blue-70;
35 | }
36 | }
37 | &.wc-stripe-chip-red {
38 | background: $wp-red-0;
39 | color: $wp-red-70;
40 | svg {
41 | fill: $wp-red-70;
42 | }
43 | }
44 | &.wc-stripe-chip-yellow {
45 | background: $wp-yellow-0;
46 | color: $wp-yellow-70;
47 | svg {
48 | fill: $wp-yellow-70;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/client/components/confirmation-modal/alert-title.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { Icon, info } from '@wordpress/icons';
4 |
5 | const AlertIcon = styled( Icon )`
6 | fill: #d94f4f;
7 | margin-right: 4px;
8 | `;
9 |
10 | const Wrapper = styled.span`
11 | display: inline-flex;
12 | align-items: center;
13 | `;
14 |
15 | const AlertTitle = ( { title } ) => (
16 |
17 |
18 | { title }
19 |
20 | );
21 |
22 | export default AlertTitle;
23 |
--------------------------------------------------------------------------------
/client/components/confirmation-modal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal } from '@wordpress/components';
3 | import classNames from 'classnames';
4 | import { HorizontalRule } from '@wordpress/primitives';
5 |
6 | import './style.scss';
7 |
8 | const ConfirmationModal = ( { children, actions, className, ...props } ) => (
9 |
13 | { children }
14 | { actions && (
15 | <>
16 |
17 |
18 | { actions }
19 |
20 | >
21 | ) }
22 |
23 | );
24 |
25 | export default ConfirmationModal;
26 |
--------------------------------------------------------------------------------
/client/components/confirmation-modal/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .wcstripe-confirmation-modal {
4 | // increasing the specificity of the styles, to ensure it is honored
5 | {&} {
6 | max-width: 600px;
7 | @media ( min-width: 600px ) {
8 | min-width: 400px;
9 | }
10 | }
11 |
12 | // to ensure that the separator extends all the way, even with different versions of Gutenberg
13 | .components-modal__content {
14 | padding: 0 $grid-unit-30 $grid-unit-30;
15 |
16 | @media ( max-width: 599px ) {
17 | display: flex;
18 | flex-direction: column;
19 |
20 | hr:last-of-type {
21 | margin-top: auto;
22 | }
23 | }
24 | }
25 |
26 | .components-modal__header {
27 | padding: 0 $grid-unit-30;
28 | @media ( max-width: 599px ) {
29 | button {
30 | display: none;
31 | }
32 | }
33 | }
34 |
35 | &__separator {
36 | margin: $grid-unit-30 -#{$grid-unit-30};
37 | }
38 |
39 | &__footer {
40 | display: flex;
41 | justify-content: flex-end;
42 |
43 | > * {
44 | &:not( :first-child ) {
45 | margin-left: $grid-unit-20;
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/client/components/inline-notice/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Notice } from '@wordpress/components';
3 | import classNames from 'classnames';
4 |
5 | import './style.scss';
6 |
7 | const InlineNotice = ( { className, ...restProps } ) => (
8 |
12 | );
13 |
14 | export default InlineNotice;
15 |
--------------------------------------------------------------------------------
/client/components/inline-notice/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .wcstripe-inline-notice {
4 | // increasing the specificity of the styles to override the Gutenberg ones
5 | {&} {
6 | margin: 0;
7 | margin-bottom: $grid-unit-20;
8 | border-left: 0;
9 | padding: 12px;
10 |
11 | >.components-notice__content {
12 | margin: 0;
13 | }
14 | &.is-dismissible {
15 | padding-right: 12px;
16 | gap: $grid-unit-20;
17 | }
18 | &.is-info {
19 | background: $wp-blue-0;
20 | }
21 | &.is-error {
22 | background: $wp-red-0;
23 | }
24 | &.is-warning {
25 | background: $wp-yellow-0;
26 | }
27 | &.is-success {
28 | background: $wp-green-0;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/client/components/loadable/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .is-loadable-placeholder {
4 | animation: loading-fade 1.6s ease-in-out infinite;
5 | background-color: $light-gray-500;
6 | color: transparent;
7 |
8 | @media screen and ( prefers-reduced-motion: reduce ) {
9 | animation: none;
10 | }
11 | display: inline-block;
12 |
13 | &.is-inline {
14 | display: inline;
15 | }
16 |
17 | &.is-block {
18 | display: block;
19 | margin-top: 1em;
20 | &:first-child {
21 | margin-top: 0;
22 | }
23 |
24 | p {
25 | margin: 0;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/client/components/loadable/test/__snapshots__/index.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Loadable when active uses children as placeholder if not passed 1`] = `
4 |
5 |
9 |
10 | Loaded content
11 |
12 |
13 |
14 | `;
15 |
16 | exports[`Loadable when inactive render children 1`] = `
17 |
18 |
19 | Loaded content
20 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/client/components/payment-method-deprecation-pill/__tests__/payment-method-deprecation-pill.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { screen, render } from '@testing-library/react';
3 | import PaymentMethodDeprecationPill from '..';
4 | import UpeToggleContext from 'wcstripe/settings/upe-toggle/context';
5 |
6 | describe( 'PaymentMethodDeprecationPill', () => {
7 | it( 'should render', () => {
8 | render(
9 |
10 |
11 |
12 | );
13 |
14 | expect( screen.queryByText( 'Deprecated' ) ).toBeInTheDocument();
15 | } );
16 | } );
17 |
--------------------------------------------------------------------------------
/client/components/payment-method-fees-pill/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import PaymentMethodFeesPill from '..';
4 |
5 | describe( 'PaymentMethodFeesPill', () => {
6 | it( 'renders the fees for the payment method when the feature flag is enabled', () => {
7 | global.__PAYMENT_METHOD_FEES_ENABLED = true;
8 | const { container } = render( );
9 |
10 | expect( container.firstChild ).not.toBeNull();
11 | } );
12 |
13 | it( 'does not render content when the feature flag is disabled', () => {
14 | const { container } = render( );
15 |
16 | expect( container.firstChild ).toBeNull();
17 | } );
18 | } );
19 |
--------------------------------------------------------------------------------
/client/components/payment-method-fees-pill/index.js:
--------------------------------------------------------------------------------
1 | import { __, sprintf } from '@wordpress/i18n';
2 | import React from 'react';
3 | import Pill from '../pill';
4 |
5 | // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
6 | const PaymentMethodFeesPill = ( { id, ...restProps } ) => {
7 | if ( __PAYMENT_METHOD_FEES_ENABLED !== true ) {
8 | return null;
9 | }
10 |
11 | // get the fees based off on the payment method's id
12 | // this is obviously hardcoded for testing purposes, since we don't have the fees yet
13 | const fees = '3.9% + $0.30';
14 |
15 | return (
16 |
24 | { fees }
25 |
26 | );
27 | };
28 |
29 | export default PaymentMethodFeesPill;
30 |
--------------------------------------------------------------------------------
/client/components/payment-method-missing-currency-pill/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { screen, render } from '@testing-library/react';
3 | import PaymentMethodMissingCurrencyPill from '..';
4 | import { usePaymentMethodCurrencies } from 'utils/use-payment-method-currencies';
5 |
6 | jest.mock( '../../../payment-methods-map', () => ( {
7 | card: { currencies: [] },
8 | giropay: { currencies: [ 'EUR' ] },
9 | } ) );
10 |
11 | jest.mock( 'utils/use-payment-method-currencies', () => ( {
12 | usePaymentMethodCurrencies: jest.fn(),
13 | } ) );
14 |
15 | describe( 'PaymentMethodMissingCurrencyPill', () => {
16 | beforeEach( () => {
17 | global.wcSettings = { currency: { code: 'USD' } };
18 | usePaymentMethodCurrencies.mockReturnValue( [ 'EUR' ] );
19 | } );
20 |
21 | it( 'should render the "Requires currency" text', () => {
22 | render(
23 |
24 | );
25 |
26 | expect( screen.queryByText( 'Requires currency' ) ).toBeInTheDocument();
27 | } );
28 |
29 | it( 'should not render when currency matches', () => {
30 | global.wcSettings = { currency: { code: 'EUR' } };
31 | const { container } = render(
32 |
33 | );
34 |
35 | expect( container.firstChild ).toBeNull();
36 | } );
37 |
38 | it( 'should render when currency differs', () => {
39 | render(
40 |
41 | );
42 |
43 | expect( screen.queryByText( 'Requires currency' ) ).toBeInTheDocument();
44 | } );
45 | } );
46 |
--------------------------------------------------------------------------------
/client/components/pill/index.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 |
3 | const Pill = styled.span`
4 | border: 1px solid #757575;
5 | border-radius: 28px;
6 | color: #757575;
7 | display: inline-block;
8 | font-size: 12px;
9 | font-weight: 400;
10 | line-height: 1.4em;
11 | padding: 2px 8px;
12 | text-align: center;
13 | width: fit-content;
14 | `;
15 |
16 | export default Pill;
17 |
--------------------------------------------------------------------------------
/client/components/popover/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Popover as PopoverComponent } from '@wordpress/components';
3 | import styled from '@emotion/styled';
4 |
5 | const StyledPopover = styled( PopoverComponent )`
6 | top: -10px !important;
7 |
8 | .components-popover__content {
9 | border: 1px solid #cccccc;
10 | border-radius: 2px;
11 | box-shadow: 0px 2px 6px 0px rgba( 0, 0, 0, 0.05 );
12 | padding: 12px;
13 | }
14 |
15 | @media ( min-width: 660px ) {
16 | .components-popover__content {
17 | width: 250px;
18 | }
19 | }
20 | `;
21 |
22 | const Popover = ( { content, BaseComponent } ) => {
23 | const [ isVisible, setIsVisible ] = useState( false );
24 |
25 | const toggleVisible = () => {
26 | setIsVisible( ( state ) => ! state );
27 | };
28 |
29 | return (
30 |
31 | { isVisible && (
32 | setIsVisible( false ) }
37 | >
38 | { content }
39 |
40 | ) }
41 |
42 | );
43 | };
44 |
45 | export default Popover;
46 |
--------------------------------------------------------------------------------
/client/components/popover/test/index.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen, act } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import Popover from '..';
4 |
5 | const DummyBaseComponent = ( { children, ...props } ) => (
6 |
7 | { children }
8 |
9 | );
10 |
11 | describe( 'Popover', () => {
12 | it( 'does not render its content initially', () => {
13 | render(
14 |
18 | );
19 |
20 | expect(
21 | screen.queryByText( 'Popover Content' )
22 | ).not.toBeInTheDocument();
23 | } );
24 |
25 | it( 'toggle the visibility on click', () => {
26 | render(
27 |
31 | );
32 |
33 | expect(
34 | screen.queryByText( 'Popover Content' )
35 | ).not.toBeInTheDocument();
36 |
37 | act( () => {
38 | userEvent.click( screen.getByTestId( 'base-component' ) );
39 | } );
40 |
41 | expect( screen.queryByText( 'Popover Content' ) ).toBeInTheDocument();
42 |
43 | act( () => {
44 | userEvent.click( screen.getByTestId( 'base-component' ) );
45 | } );
46 |
47 | expect(
48 | screen.queryByText( 'Popover Content' )
49 | ).not.toBeInTheDocument();
50 | } );
51 | } );
52 |
--------------------------------------------------------------------------------
/client/components/recurring-payment-icon/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/components/recurring-payment-icon/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import styled from '@emotion/styled';
4 | import icon from './icon.svg';
5 | import Tooltip from 'wcstripe/components/tooltip';
6 |
7 | const Icon = styled.img`
8 | height: 14px;
9 | width: 14px;
10 | `;
11 |
12 | const StyledTooltip = styled( Tooltip )`
13 | border-radius: 4px;
14 | padding: 5px 10px;
15 | `;
16 |
17 | const RecurringPaymentIcon = () => {
18 | return (
19 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default RecurringPaymentIcon;
31 |
--------------------------------------------------------------------------------
/client/components/stripe-banner/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import icon from './icon.svg';
4 |
5 | const Image = styled.img`
6 | max-width: 100%;
7 | width: 100%;
8 | `;
9 |
10 | const StripeBanner = () => ;
11 |
12 | export default StripeBanner;
13 |
--------------------------------------------------------------------------------
/client/components/tooltip/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { noop } from 'lodash';
3 | import TooltipBase from './tooltip-base';
4 |
5 | const Tooltip = ( { isVisible, onHide = noop, ...props } ) => {
6 | const [ isHovered, setIsHovered ] = useState( false );
7 | const [ isClicked, setIsClicked ] = useState( false );
8 |
9 | const handleMouseEnter = () => {
10 | setIsHovered( true );
11 | };
12 | const handleMouseLeave = () => {
13 | setIsHovered( false );
14 | onHide();
15 | };
16 | const handleMouseClick = ( event ) => {
17 | event.preventDefault();
18 | setIsClicked( ( val ) => ! val );
19 | if ( isClicked ) {
20 | onHide();
21 | }
22 | };
23 | const handleHide = () => {
24 | setIsHovered( false );
25 | setIsClicked( false );
26 | onHide();
27 | };
28 |
29 | return (
30 |
46 | );
47 | };
48 |
49 | export default Tooltip;
50 |
--------------------------------------------------------------------------------
/client/components/tooltip/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .wcstripe-tooltip {
4 | &__content-wrapper {
5 | // ensures that the element needed for position calculations isn't included in the DOM layout
6 | display: contents;
7 |
8 | // Ensure the TooltipBase content is centered within the button. The structure is button > div > TooltipBase content.
9 | div.wcstripe-tooltip__content-wrapper > * {
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | }
14 | }
15 |
16 | &__tooltip-wrapper {
17 | visibility: hidden;
18 | position: fixed;
19 | opacity: 0;
20 | transition: opacity 150ms ease-in;
21 | // gotta do it a bit higher than the modal used in Gutenberg, to ensure the tooltip appears on top 😅
22 | z-index: 100010;
23 |
24 | &.is-hiding {
25 | opacity: 0 !important;
26 | transition: opacity 200ms ease-out 300ms;
27 | }
28 | }
29 |
30 | &__tooltip {
31 | position: relative;
32 |
33 | color: $white;
34 | background-color: $gray-900;
35 | padding: 10px;
36 | text-align: center;
37 | text-wrap: balance;
38 |
39 | &::after {
40 | content: ' ';
41 | position: absolute;
42 |
43 | // assuming all the tooltips are displayed at the top of the wrapped element.
44 | // no need to complicate things since that's the only use case at the moment.
45 | bottom: 0;
46 | left: 50%;
47 | transform: translate(-50%, 22px);
48 | border: solid 15px transparent;
49 | border-top-color: $gray-900;
50 | }
51 |
52 | &-left-top {
53 | &::after {
54 | left: 0;
55 | transform: translate(0, 22px);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/client/components/webhook-description/warning-icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GridIcon from 'gridicons';
3 |
4 | const WarningIcon = () => {
5 | return (
6 |
7 |
15 |
16 | );
17 | };
18 |
19 | export default WarningIcon;
20 |
--------------------------------------------------------------------------------
/client/components/webhook-information/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { WebhookInformation } from '..';
4 |
5 | describe( 'WebhookInformation', () => {
6 | it( 'Renders the WebhookInformation component', () => {
7 | const { container } = render( );
8 |
9 | expect( container.firstChild ).not.toBeNull();
10 | expect( container.firstChild ).toHaveTextContent(
11 | 'Click the Configure connection button to configure a webhook(opens in a new tab). This will complete your Stripe account connection process.'
12 | );
13 | } );
14 | } );
15 |
--------------------------------------------------------------------------------
/client/components/webhook-information/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import { ExternalLink } from '@wordpress/components';
3 | import styled from '@emotion/styled';
4 | import interpolateComponents from 'interpolate-components';
5 |
6 | const WebhookButtonText = styled.strong`
7 | padding: 0 2px;
8 | background-color: #f6f7f7; // $studio-gray-0
9 | `;
10 |
11 | export const WebhookInformation = () => {
12 | return (
13 |
14 | { interpolateComponents( {
15 | mixedString: __(
16 | 'Click the {{configureButtonText/}} button to {{settingsLink}}configure a webhook{{/settingsLink}}. This will complete your Stripe account connection process.',
17 | 'woocommerce-gateway-stripe'
18 | ),
19 | components: {
20 | configureButtonText: (
21 |
22 | Configure connection
23 |
24 | ),
25 | settingsLink: (
26 |
27 | ),
28 | },
29 | } ) }
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/client/data/account-keys/__tests__/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { getAccountKeys, isSavingAccountKeys } from '../selectors';
2 |
3 | describe( 'Account keys selectors tests', () => {
4 | describe( 'getAccountKeys()', () => {
5 | test( 'returns the value of state.accountKeys.data', () => {
6 | const state = {
7 | accountKeys: {
8 | data: {
9 | foo: 'bar',
10 | },
11 | },
12 | };
13 |
14 | expect( getAccountKeys( state ) ).toEqual( { foo: 'bar' } );
15 | } );
16 |
17 | test.each( [ [ undefined ], [ {} ], [ { accountKeys: {} } ] ] )(
18 | 'returns {} if key is missing (tested state: %j)',
19 | ( state ) => {
20 | expect( getAccountKeys( state ) ).toEqual( {} );
21 | }
22 | );
23 | } );
24 |
25 | describe( 'isSavingAccountKeys()', () => {
26 | test( 'returns the value of state.accountKeys.isSaving', () => {
27 | const state = {
28 | accountKeys: {
29 | isSaving: true,
30 | },
31 | };
32 |
33 | expect( isSavingAccountKeys( state ) ).toBeTruthy();
34 | } );
35 |
36 | test.each( [ [ undefined ], [ {} ], [ { accountKeys: {} } ] ] )(
37 | 'returns false if missing (tested state: %j)',
38 | ( state ) => {
39 | expect( isSavingAccountKeys( state ) ).toBeFalsy();
40 | }
41 | );
42 | } );
43 | } );
44 |
--------------------------------------------------------------------------------
/client/data/account-keys/action-types.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_ACCOUNT_KEYS: 'SET_ACCOUNT_KEYS',
3 | SET_ACCOUNT_KEYS_VALUES: 'SET_ACCOUNT_KEYS_VALUES',
4 | SET_IS_SAVING_ACCOUNT_KEYS: 'SET_IS_SAVING_ACCOUNT_KEYS',
5 | SET_IS_TESTING_ACCOUNT_KEYS: 'SET_IS_TESTING_ACCOUNT_KEYS',
6 | SET_IS_VALID_KEYS: 'SET_IS_VALID_KEYS',
7 | SET_IS_CONFIGURING_WEBHOOKS: 'SET_IS_CONFIGURING_WEBHOOKS',
8 | };
9 |
--------------------------------------------------------------------------------
/client/data/account-keys/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 | import * as selectors from './selectors';
3 | import * as actions from './actions';
4 | import * as resolvers from './resolvers';
5 |
6 | export { reducer, selectors, actions, resolvers };
7 | export * from './hooks';
8 |
--------------------------------------------------------------------------------
/client/data/account-keys/reducer.js:
--------------------------------------------------------------------------------
1 | import ACTION_TYPES from './action-types';
2 |
3 | const defaultState = {
4 | isTesting: false,
5 | isValid: null,
6 | isSaving: false,
7 | savingError: null,
8 | data: {},
9 | isConfiguringWebhooks: false,
10 | };
11 |
12 | export const accountKeysReducer = (
13 | state = defaultState,
14 | { type, ...action }
15 | ) => {
16 | switch ( type ) {
17 | case ACTION_TYPES.SET_ACCOUNT_KEYS:
18 | return {
19 | ...state,
20 | data: action.payload,
21 | };
22 |
23 | case ACTION_TYPES.SET_ACCOUNT_KEYS_VALUES:
24 | return {
25 | ...state,
26 | savingError: null,
27 | data: {
28 | ...state.data,
29 | ...action.payload,
30 | },
31 | };
32 |
33 | case ACTION_TYPES.SET_IS_SAVING_ACCOUNT_KEYS:
34 | return {
35 | ...state,
36 | isSaving: action.isSaving,
37 | savingError: action.error,
38 | };
39 |
40 | case ACTION_TYPES.SET_IS_TESTING_ACCOUNT_KEYS:
41 | return {
42 | ...state,
43 | isTesting: action.isTesting,
44 | };
45 |
46 | case ACTION_TYPES.SET_IS_VALID_KEYS:
47 | return {
48 | ...state,
49 | isValid: action.isValid,
50 | };
51 |
52 | case ACTION_TYPES.SET_IS_CONFIGURING_WEBHOOKS:
53 | return {
54 | ...state,
55 | isConfiguringWebhooks: action.isProcessing,
56 | };
57 | }
58 |
59 | return state;
60 | };
61 |
62 | export default accountKeysReducer;
63 |
--------------------------------------------------------------------------------
/client/data/account-keys/resolvers.js:
--------------------------------------------------------------------------------
1 | import { dispatch } from '@wordpress/data';
2 | import { __ } from '@wordpress/i18n';
3 | import { apiFetch } from '@wordpress/data-controls';
4 | import { NAMESPACE } from '../constants';
5 | import { updateAccountKeys } from './actions';
6 |
7 | /**
8 | * Retrieve settings from the site's REST API.
9 | */
10 | export function* getAccountKeys() {
11 | const path = `${ NAMESPACE }/account_keys`;
12 |
13 | try {
14 | const result = yield apiFetch( { path } );
15 | yield updateAccountKeys( result );
16 | } catch ( e ) {
17 | yield dispatch( 'core/notices' ).createErrorNotice(
18 | __( 'Error retrieving account keys.', 'woocommerce-gateway-stripe' )
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/data/account-keys/selectors.js:
--------------------------------------------------------------------------------
1 | const EMPTY_OBJ = {};
2 |
3 | const getAccountKeysState = ( state ) => {
4 | if ( ! state ) {
5 | return EMPTY_OBJ;
6 | }
7 |
8 | return state.accountKeys || EMPTY_OBJ;
9 | };
10 |
11 | export const getAccountKeys = ( state ) => {
12 | return getAccountKeysState( state ).data || EMPTY_OBJ;
13 | };
14 |
15 | export const isSavingAccountKeys = ( state ) => {
16 | return getAccountKeysState( state ).isSaving || false;
17 | };
18 |
19 | export const getAccountKeysSavingError = ( state ) => {
20 | return getAccountKeysState( state ).savingError;
21 | };
22 |
23 | export const getIsTestingAccountKeys = ( state ) => {
24 | return getAccountKeysState( state ).isTesting || false;
25 | };
26 |
27 | export const getIsValidAccountKeys = ( state ) => {
28 | return getAccountKeysState( state ).isValid;
29 | };
30 |
31 | export const isConfiguringWebhooks = ( state ) => {
32 | return getAccountKeysState( state ).isConfiguringWebhooks || false;
33 | };
34 |
--------------------------------------------------------------------------------
/client/data/account/__tests__/actions.test.js:
--------------------------------------------------------------------------------
1 | import { select, dispatch } from '@wordpress/data';
2 | import { apiFetch } from '@wordpress/data-controls';
3 | import { refreshAccount } from '../actions';
4 |
5 | jest.mock( '@wordpress/data' );
6 | jest.mock( '@wordpress/data-controls' );
7 |
8 | describe( 'Account actions tests', () => {
9 | describe( 'refreshAccount()', () => {
10 | beforeEach( () => {
11 | const noticesDispatch = {
12 | createErrorNotice: jest.fn(),
13 | createSuccessNotice: jest.fn(),
14 | };
15 |
16 | apiFetch.mockImplementation( () => {} );
17 | dispatch.mockImplementation( ( storeName ) => {
18 | if ( storeName === 'core/notices' ) {
19 | return noticesDispatch;
20 | }
21 |
22 | return {};
23 | } );
24 |
25 | select.mockImplementation( () => {
26 | return {
27 | getAccountCapabilitiesByStatus: () => {
28 | return [];
29 | },
30 | };
31 | } );
32 | } );
33 |
34 | it( 'retrieves and stores account data', () => {
35 | apiFetch.mockReturnValue( 'api response' );
36 |
37 | const yielded = [ ...refreshAccount() ];
38 |
39 | expect( apiFetch ).toHaveBeenCalledWith( {
40 | path: '/wc/v3/wc_stripe/account/refresh',
41 | method: 'POST',
42 | } );
43 | expect( yielded ).toContainEqual(
44 | expect.objectContaining( {
45 | type: 'SET_IS_REFRESHING',
46 | isRefreshing: true,
47 | } )
48 | );
49 | expect( yielded ).toContainEqual(
50 | expect.objectContaining( {
51 | type: 'SET_IS_REFRESHING',
52 | isRefreshing: false,
53 | } )
54 | );
55 | } );
56 | } );
57 | } );
58 |
--------------------------------------------------------------------------------
/client/data/account/__tests__/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { getAccountData, isRefreshingAccount } from '../selectors';
2 |
3 | describe( 'Account selectors tests', () => {
4 | describe( 'getAccountData()', () => {
5 | test( 'returns the value of state.account.data', () => {
6 | const state = {
7 | account: {
8 | data: {
9 | foo: 'bar',
10 | },
11 | },
12 | };
13 |
14 | expect( getAccountData( state ) ).toEqual( { foo: 'bar' } );
15 | } );
16 |
17 | test.each( [ [ undefined ], [ {} ], [ { account: {} } ] ] )(
18 | 'returns {} if key is missing (tested state: %j)',
19 | ( state ) => {
20 | expect( getAccountData( state ) ).toEqual( {} );
21 | }
22 | );
23 | } );
24 |
25 | describe( 'isRefreshingAccount()', () => {
26 | test( 'returns the value of state.account.isRefreshing', () => {
27 | const state = {
28 | account: {
29 | isRefreshing: true,
30 | },
31 | };
32 |
33 | expect( isRefreshingAccount( state ) ).toBeTruthy();
34 | } );
35 |
36 | test.each( [ [ undefined ], [ {} ], [ { account: {} } ] ] )(
37 | 'returns false if missing (tested state: %j)',
38 | ( state ) => {
39 | expect( isRefreshingAccount( state ) ).toBeFalsy();
40 | }
41 | );
42 | } );
43 | } );
44 |
--------------------------------------------------------------------------------
/client/data/account/action-types.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_ACCOUNT: 'SET_ACCOUNT',
3 | SET_IS_REFRESHING: 'SET_IS_REFRESHING',
4 | };
5 |
--------------------------------------------------------------------------------
/client/data/account/hooks.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelect } from '@wordpress/data';
2 | import { STORE_NAME } from '../constants';
3 |
4 | export const useAccount = () => {
5 | const { refreshAccount } = useDispatch( STORE_NAME );
6 |
7 | const data = useSelect( ( select ) => {
8 | const { getAccountData } = select( STORE_NAME );
9 |
10 | return getAccountData();
11 | }, [] );
12 |
13 | const isLoading = useSelect( ( select ) => {
14 | const { hasFinishedResolution, isResolving } = select( STORE_NAME );
15 |
16 | return (
17 | isResolving( 'getAccountData' ) ||
18 | ! hasFinishedResolution( 'getAccountData' )
19 | );
20 | }, [] );
21 |
22 | const isRefreshing = useSelect( ( select ) => {
23 | const { isRefreshingAccount } = select( STORE_NAME );
24 |
25 | return isRefreshingAccount();
26 | }, [] );
27 |
28 | return { data, isLoading, isRefreshing, refreshAccount };
29 | };
30 |
31 | export const useGetCapabilities = () => {
32 | return useSelect( ( select ) => {
33 | const { getAccountCapabilities } = select( STORE_NAME );
34 |
35 | return getAccountCapabilities();
36 | }, [] );
37 | };
38 |
--------------------------------------------------------------------------------
/client/data/account/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 | import * as selectors from './selectors';
3 | import * as actions from './actions';
4 | import * as resolvers from './resolvers';
5 |
6 | export { reducer, selectors, actions, resolvers };
7 | export * from './hooks';
8 |
--------------------------------------------------------------------------------
/client/data/account/reducer.js:
--------------------------------------------------------------------------------
1 | import ACTION_TYPES from './action-types';
2 |
3 | const defaultState = {
4 | isRefreshing: false,
5 | data: {},
6 | };
7 |
8 | export const accountReducer = ( state = defaultState, { type, ...action } ) => {
9 | switch ( type ) {
10 | case ACTION_TYPES.SET_ACCOUNT:
11 | return {
12 | ...state,
13 | data: action.payload,
14 | };
15 |
16 | case ACTION_TYPES.SET_IS_REFRESHING:
17 | return {
18 | ...state,
19 | isRefreshing: action.isRefreshing,
20 | };
21 | }
22 |
23 | return state;
24 | };
25 |
26 | export default accountReducer;
27 |
--------------------------------------------------------------------------------
/client/data/account/resolvers.js:
--------------------------------------------------------------------------------
1 | import { dispatch } from '@wordpress/data';
2 | import { __ } from '@wordpress/i18n';
3 | import { apiFetch } from '@wordpress/data-controls';
4 | import { NAMESPACE } from '../constants';
5 | import { updateAccount } from './actions';
6 |
7 | /**
8 | * Retrieve the account data from the site's REST API.
9 | */
10 | export function* getAccountData() {
11 | const path = `${ NAMESPACE }/account`;
12 |
13 | try {
14 | const result = yield apiFetch( { path } );
15 | yield updateAccount( result );
16 | } catch ( e ) {
17 | yield dispatch( 'core/notices' ).createErrorNotice(
18 | __( 'Error retrieving account data.', 'woocommerce-gateway-stripe' )
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/data/account/selectors.js:
--------------------------------------------------------------------------------
1 | const EMPTY_OBJ = {};
2 |
3 | const getAccountState = ( state ) => {
4 | if ( ! state ) {
5 | return EMPTY_OBJ;
6 | }
7 |
8 | return state.account || EMPTY_OBJ;
9 | };
10 |
11 | export const getAccountData = ( state ) => {
12 | return getAccountState( state ).data || EMPTY_OBJ;
13 | };
14 |
15 | export const getAccountCapabilities = ( state ) => {
16 | return getAccountData( state ).account?.capabilities ?? EMPTY_OBJ;
17 | };
18 |
19 | export const getAccountCapabilitiesByStatus = ( state, searchedStatus ) => {
20 | const capabilities = getAccountCapabilities( state );
21 |
22 | const filteredCapabilities = Object.entries( capabilities ).reduce(
23 | ( capabilitiesByStatus, [ capability, status ] ) => {
24 | if ( status === searchedStatus ) {
25 | capabilitiesByStatus.push( capability );
26 | }
27 | return capabilitiesByStatus;
28 | },
29 | []
30 | );
31 |
32 | return filteredCapabilities;
33 | };
34 |
35 | export const isRefreshingAccount = ( state ) => {
36 | return getAccountState( state ).isRefreshing || false;
37 | };
38 |
--------------------------------------------------------------------------------
/client/data/constants.js:
--------------------------------------------------------------------------------
1 | export const NAMESPACE = '/wc/v3/wc_stripe';
2 | export const STORE_NAME = 'wc/stripe';
3 |
4 | /**
5 | * The amount threshold for displaying the notice.
6 | *
7 | * @type {number} The threshold amount.
8 | */
9 | export const CASH_APP_NOTICE_AMOUNT_THRESHOLD = 200000;
10 |
11 | /**
12 | * Wait time in ms for a notice to be displayed in ECE before proceeding with the checkout process.
13 | *
14 | * Reasons for this value:
15 | * - We cannot display an alert message because it blocks the default ECE process
16 | * - The delay cannot be higher than 1s due to Stripe JS limitations (it times out after 1s)
17 | *
18 | * @type {number} The delay in milliseconds.
19 | */
20 | export const EXPRESS_CHECKOUT_NOTICE_DELAY = 700;
21 |
--------------------------------------------------------------------------------
/client/data/index.js:
--------------------------------------------------------------------------------
1 | import { STORE_NAME } from './constants';
2 | import { initStore } from './store';
3 |
4 | initStore();
5 |
6 | export const WC_STRIPE_STORE_NAME = STORE_NAME;
7 |
8 | // We only ask for hooks when importing directly from 'wcstripe/data'.
9 | export * from './settings/hooks';
10 | export * from './payment-gateway/hooks';
11 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/__tests__/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { getPaymentGateway, isSavingPaymentGateway } from '../selectors';
2 |
3 | describe( 'Payment gateway selectors tests', () => {
4 | describe( 'getPaymentGateway()', () => {
5 | test( 'returns the value of state.paymentGateway.data', () => {
6 | const state = {
7 | paymentGateway: {
8 | data: {
9 | foo: 'bar',
10 | },
11 | },
12 | };
13 |
14 | expect( getPaymentGateway( state ) ).toEqual( { foo: 'bar' } );
15 | } );
16 |
17 | test.each( [ [ undefined ], [ {} ], [ { paymentGateway: {} } ] ] )(
18 | 'returns {} if key is missing (tested state: %j)',
19 | ( state ) => {
20 | expect( getPaymentGateway( state ) ).toEqual( {} );
21 | }
22 | );
23 | } );
24 |
25 | describe( 'isSavingPaymentGateway()', () => {
26 | test( 'returns the value of state.paymentGateway.isSaving', () => {
27 | const state = {
28 | paymentGateway: {
29 | isSaving: true,
30 | },
31 | };
32 |
33 | expect( isSavingPaymentGateway( state ) ).toBeTruthy();
34 | } );
35 |
36 | test.each( [ [ undefined ], [ {} ], [ { paymentGateway: {} } ] ] )(
37 | 'returns false if missing (tested state: %j)',
38 | ( state ) => {
39 | expect( isSavingPaymentGateway( state ) ).toBeFalsy();
40 | }
41 | );
42 | } );
43 | } );
44 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/action-types.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_PAYMENT_GATEWAY: 'SET_PAYMENT_GATEWAY',
3 | SET_PAYMENT_GATEWAY_VALUES: 'SET_PAYMENT_GATEWAY_VALUES',
4 | SET_IS_SAVING_PAYMENT_GATEWAY: 'SET_IS_SAVING_PAYMENT_GATEWAY',
5 | };
6 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/actions.js:
--------------------------------------------------------------------------------
1 | import { dispatch, select } from '@wordpress/data';
2 | import { __ } from '@wordpress/i18n';
3 | import { apiFetch } from '@wordpress/data-controls';
4 | import { getQuery } from '@woocommerce/navigation';
5 | import { NAMESPACE, STORE_NAME } from '../constants';
6 | import ACTION_TYPES from './action-types';
7 |
8 | export function updatePaymentGatewayValues( payload ) {
9 | return {
10 | type: ACTION_TYPES.SET_PAYMENT_GATEWAY_VALUES,
11 | payload,
12 | };
13 | }
14 |
15 | export function updatePaymentGateway( data ) {
16 | return {
17 | type: ACTION_TYPES.SET_PAYMENT_GATEWAY,
18 | data,
19 | };
20 | }
21 |
22 | export function updateIsSavingPaymentGateway( isSaving, error ) {
23 | return {
24 | type: ACTION_TYPES.SET_IS_SAVING_PAYMENT_GATEWAY,
25 | isSaving,
26 | error,
27 | };
28 | }
29 |
30 | export function* savePaymentGateway() {
31 | let error = null;
32 | const { section } = getQuery();
33 | try {
34 | const settings = select( STORE_NAME ).getPaymentGateway();
35 |
36 | yield updateIsSavingPaymentGateway( true, null );
37 |
38 | yield apiFetch( {
39 | path: `${ NAMESPACE }/payment-gateway/${ section }`,
40 | method: 'post',
41 | data: settings,
42 | } );
43 |
44 | yield dispatch( 'core/notices' ).createSuccessNotice(
45 | __( 'Settings saved.', 'woocommerce-gateway-stripe' )
46 | );
47 | } catch ( e ) {
48 | error = e;
49 | yield dispatch( 'core/notices' ).createErrorNotice(
50 | __( 'Error saving settings.', 'woocommerce-gateway-stripe' )
51 | );
52 | } finally {
53 | yield updateIsSavingPaymentGateway( false, error );
54 | }
55 |
56 | return error === null;
57 | }
58 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 | import * as selectors from './selectors';
3 | import * as actions from './actions';
4 | import * as resolvers from './resolvers';
5 |
6 | export { reducer, selectors, actions, resolvers };
7 | export * from './hooks';
8 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/reducer.js:
--------------------------------------------------------------------------------
1 | import ACTION_TYPES from './action-types';
2 |
3 | const defaultState = {
4 | isSaving: false,
5 | savingError: null,
6 | data: {},
7 | };
8 |
9 | export const receivePaymentGateway = (
10 | state = defaultState,
11 | { type, ...action }
12 | ) => {
13 | switch ( type ) {
14 | case ACTION_TYPES.SET_PAYMENT_GATEWAY:
15 | return {
16 | ...state,
17 | data: action.data,
18 | };
19 |
20 | case ACTION_TYPES.SET_PAYMENT_GATEWAY_VALUES:
21 | return {
22 | ...state,
23 | savingError: null,
24 | data: {
25 | ...state.data,
26 | ...action.payload,
27 | },
28 | };
29 |
30 | case ACTION_TYPES.SET_IS_SAVING_PAYMENT_GATEWAY:
31 | return {
32 | ...state,
33 | isSaving: action.isSaving,
34 | savingError: action.error,
35 | };
36 | }
37 |
38 | return state;
39 | };
40 |
41 | export default receivePaymentGateway;
42 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/resolvers.js:
--------------------------------------------------------------------------------
1 | import { dispatch } from '@wordpress/data';
2 | import { __ } from '@wordpress/i18n';
3 | import { apiFetch } from '@wordpress/data-controls';
4 | import { getQuery } from '@woocommerce/navigation';
5 | import { NAMESPACE } from '../constants';
6 | import { updatePaymentGateway } from './actions';
7 |
8 | const { section } = getQuery();
9 |
10 | /**
11 | * Retrieve payment gateway settings from the site's REST API.
12 | */
13 | export function* getPaymentGateway() {
14 | const path = `${ NAMESPACE }/payment-gateway/${ section }`;
15 |
16 | try {
17 | const result = yield apiFetch( { path } );
18 | yield updatePaymentGateway( result );
19 | } catch ( e ) {
20 | yield dispatch( 'core/notices' ).createErrorNotice(
21 | __(
22 | 'Error retrieving payment gateway settings.',
23 | 'woocommerce-gateway-stripe'
24 | )
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/data/payment-gateway/selectors.js:
--------------------------------------------------------------------------------
1 | const EMPTY_OBJ = {};
2 |
3 | const getPaymentGatewayState = ( state ) => {
4 | if ( ! state ) {
5 | return EMPTY_OBJ;
6 | }
7 |
8 | return state.paymentGateway || EMPTY_OBJ;
9 | };
10 |
11 | export const getPaymentGateway = ( state ) => {
12 | return getPaymentGatewayState( state ).data || EMPTY_OBJ;
13 | };
14 |
15 | export const isSavingPaymentGateway = ( state ) => {
16 | return getPaymentGatewayState( state ).isSaving || false;
17 | };
18 |
19 | export const getPaymentGatewaySavingError = ( state ) => {
20 | return getPaymentGatewayState( state ).savingError;
21 | };
22 |
--------------------------------------------------------------------------------
/client/data/settings/action-types.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_SETTINGS: 'SET_SETTINGS',
3 | SET_SETTINGS_VALUES: 'SET_SETTINGS_VALUES',
4 | SET_IS_SAVING_SETTINGS: 'SET_IS_SAVING_SETTINGS',
5 | SET_IS_SAVING_ORDERED_PAYMENT_METHOD_IDS:
6 | 'SET_IS_SAVING_ORDERED_PAYMENT_METHOD_IDS',
7 | SET_IS_CUSTOMIZING_PAYMENT_METHOD: 'SET_IS_CUSTOMIZING_PAYMENT_METHOD',
8 | };
9 |
--------------------------------------------------------------------------------
/client/data/settings/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducer';
2 | import * as selectors from './selectors';
3 | import * as actions from './actions';
4 | import * as resolvers from './resolvers';
5 |
6 | export { reducer, selectors, actions, resolvers };
7 | export * from './hooks';
8 |
--------------------------------------------------------------------------------
/client/data/settings/reducer.js:
--------------------------------------------------------------------------------
1 | import ACTION_TYPES from './action-types';
2 |
3 | const defaultState = {
4 | isSaving: false,
5 | savingError: null,
6 | isSavingOrderedPaymentMethodIds: false,
7 | isCustomizingPaymentMethod: false,
8 | data: {},
9 | };
10 |
11 | export const receiveSettings = (
12 | state = defaultState,
13 | { type, ...action }
14 | ) => {
15 | switch ( type ) {
16 | case ACTION_TYPES.SET_SETTINGS:
17 | return {
18 | ...state,
19 | data: action.data,
20 | };
21 |
22 | case ACTION_TYPES.SET_SETTINGS_VALUES:
23 | return {
24 | ...state,
25 | savingError: null,
26 | data: {
27 | ...state.data,
28 | ...action.payload,
29 | },
30 | };
31 |
32 | case ACTION_TYPES.SET_IS_SAVING_SETTINGS:
33 | return {
34 | ...state,
35 | isSaving: action.isSaving,
36 | savingError: action.error,
37 | };
38 | case ACTION_TYPES.SET_IS_SAVING_ORDERED_PAYMENT_METHOD_IDS:
39 | return {
40 | ...state,
41 | isSavingOrderedPaymentMethodIds:
42 | action.isSavingOrderedPaymentMethodIds,
43 | };
44 | case ACTION_TYPES.SET_IS_CUSTOMIZING_PAYMENT_METHOD:
45 | return {
46 | ...state,
47 | isCustomizingPaymentMethod: action.isCustomizingPaymentMethod,
48 | };
49 | }
50 |
51 | return state;
52 | };
53 |
54 | export default receiveSettings;
55 |
--------------------------------------------------------------------------------
/client/data/settings/resolvers.js:
--------------------------------------------------------------------------------
1 | import { dispatch } from '@wordpress/data';
2 | import { __ } from '@wordpress/i18n';
3 | import { apiFetch } from '@wordpress/data-controls';
4 | import { NAMESPACE } from '../constants';
5 | import { updateSettings } from './actions';
6 |
7 | /**
8 | * Retrieve settings from the site's REST API.
9 | */
10 | export function* getSettings() {
11 | const path = `${ NAMESPACE }/settings`;
12 |
13 | try {
14 | const result = yield apiFetch( { path } );
15 | yield updateSettings( result );
16 | } catch ( e ) {
17 | yield dispatch( 'core/notices' ).createErrorNotice(
18 | __( 'Error retrieving settings.', 'woocommerce-gateway-stripe' )
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/data/settings/selectors.js:
--------------------------------------------------------------------------------
1 | const EMPTY_OBJ = {};
2 |
3 | const getSettingsState = ( state ) => {
4 | if ( ! state ) {
5 | return EMPTY_OBJ;
6 | }
7 |
8 | return state.settings || EMPTY_OBJ;
9 | };
10 |
11 | export const getSettings = ( state ) => {
12 | return getSettingsState( state ).data || EMPTY_OBJ;
13 | };
14 |
15 | export const isSavingSettings = ( state ) => {
16 | return getSettingsState( state ).isSaving || false;
17 | };
18 |
19 | export const getSavingError = ( state ) => {
20 | return getSettingsState( state ).savingError;
21 | };
22 |
23 | export const getOrderedPaymentMethodIds = ( state ) => {
24 | return (
25 | getSettingsState( state ).data?.ordered_payment_method_ids || EMPTY_OBJ
26 | );
27 | };
28 |
29 | export const isSavingOrderedPaymentMethodIds = ( state ) => {
30 | return getSettingsState( state ).isSavingOrderedPaymentMethodIds || false;
31 | };
32 |
33 | export const getIndividualPaymentMethodSettings = ( state ) => {
34 | return (
35 | getSettingsState( state ).data?.individual_payment_method_settings ||
36 | EMPTY_OBJ
37 | );
38 | };
39 |
40 | export const isCustomizingPaymentMethod = ( state ) => {
41 | return getSettingsState( state ).isCustomizingPaymentMethod || false;
42 | };
43 |
--------------------------------------------------------------------------------
/client/data/store.js:
--------------------------------------------------------------------------------
1 | import { registerStore, combineReducers } from '@wordpress/data';
2 | import { controls } from '@wordpress/data-controls';
3 | import { STORE_NAME } from './constants';
4 | import * as settings from './settings';
5 | import * as account from './account';
6 | import * as accountKeys from './account-keys';
7 | import * as paymentGateway from './payment-gateway';
8 |
9 | const actions = {};
10 | const selectors = {};
11 | const resolvers = {};
12 |
13 | [ settings, account, accountKeys, paymentGateway ].forEach( ( item ) => {
14 | Object.assign( actions, { ...item.actions } );
15 | Object.assign( selectors, { ...item.selectors } );
16 | Object.assign( resolvers, { ...item.resolvers } );
17 | } );
18 |
19 | // Extracted into wrapper function to facilitate testing.
20 | export const initStore = () =>
21 | registerStore( STORE_NAME, {
22 | reducer: combineReducers( {
23 | settings: settings.reducer,
24 | account: account.reducer,
25 | accountKeys: accountKeys.reducer,
26 | paymentGateway: paymentGateway.reducer,
27 | } ),
28 | controls,
29 | actions,
30 | selectors,
31 | resolvers,
32 | } );
33 |
--------------------------------------------------------------------------------
/client/entrypoints/amazon-pay-settings/amazon-pay-enable-section.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { Card, CheckboxControl } from '@wordpress/components';
4 | import { useAmazonPayEnabledSettings } from 'wcstripe/data';
5 | import CardBody from 'wcstripe/settings/card-body';
6 | const AmazonPayEnableSection = () => {
7 | const [
8 | isAmazonPayEnabled,
9 | updateIsAmazonPayEnabled,
10 | ] = useAmazonPayEnabledSettings();
11 |
12 | return (
13 |
14 |
15 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default AmazonPayEnableSection;
34 |
--------------------------------------------------------------------------------
/client/entrypoints/amazon-pay-settings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import AmazonPayPage from './amazon-pay-page';
4 |
5 | const amazonPayContainer = document.getElementById(
6 | 'wc-stripe-amazon-pay-settings-container'
7 | );
8 |
9 | if ( amazonPayContainer ) {
10 | ReactDOM.render( , amazonPayContainer );
11 | }
12 |
--------------------------------------------------------------------------------
/client/entrypoints/express-checkout/styles.scss:
--------------------------------------------------------------------------------
1 | #wc-stripe-express-checkout-element iframe {
2 | max-width: unset;
3 | }
4 |
5 | #wc-stripe-express-checkout-element {
6 | margin-bottom: 12px;
7 | display: flex;
8 | gap: 10px;
9 | flex-wrap: wrap;
10 | justify-content: center;
11 |
12 | > div {
13 | flex: 1;
14 | min-width: 260px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-gateways/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import DisableConfirmationModal from '../disable-confirmation-modal';
4 |
5 | jest.mock( '../../../data', () => ( {
6 | useEnabledPaymentMethodIds: jest.fn().mockReturnValue( [ [] ] ),
7 | usePaymentRequestEnabledSettings: jest.fn().mockReturnValue( '' ),
8 | } ) );
9 |
10 | describe( 'DisableConfirmationModal', () => {
11 | it( 'calls the onClose handler on cancel', () => {
12 | const handleCloseMock = jest.fn();
13 | render( );
14 |
15 | expect( handleCloseMock ).not.toHaveBeenCalled();
16 |
17 | userEvent.click( screen.getByText( 'Cancel' ) );
18 |
19 | expect( handleCloseMock ).toHaveBeenCalled();
20 | } );
21 |
22 | it( 'calls the onConfirm handler on cancel', () => {
23 | const handleConfirmMock = jest.fn();
24 | render( );
25 |
26 | expect( handleConfirmMock ).not.toHaveBeenCalled();
27 |
28 | userEvent.click( screen.getByText( 'Disable' ) );
29 |
30 | expect( handleConfirmMock ).toHaveBeenCalled();
31 | } );
32 | } );
33 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-gateways/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PaymentGatewaysConfirmation from './payment-gateways-confirmation';
4 |
5 | const paymentGatewaysContainer = document.getElementById(
6 | 'wc-stripe-payment-gateways-container'
7 | );
8 | if ( paymentGatewaysContainer ) {
9 | ReactDOM.render(
10 | ,
11 | paymentGatewaysContainer
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-gateways/style.scss:
--------------------------------------------------------------------------------
1 | .disable-confirmation-modal {
2 | &__payment-methods-list {
3 | margin-bottom: 32px;
4 |
5 | > * {
6 | &:not( :last-child ) {
7 | margin-bottom: 12px;
8 | }
9 | }
10 |
11 | // Hotfix for Stripe Gateway - might need to be fixed in WCPay as well.
12 | .woocommerce-gateway-stripe__payment-method-icon {
13 | max-width: inherit;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-request-settings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PaymentRequestsPage from './payment-request-page';
4 |
5 | const container = document.getElementById(
6 | 'wc-stripe-payment-request-settings-container'
7 | );
8 |
9 | if ( container ) {
10 | ReactDOM.render( , container );
11 | }
12 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-request-settings/payment-request-enable-section.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { Card, CheckboxControl } from '@wordpress/components';
4 | import { usePaymentRequestEnabledSettings } from 'wcstripe/data';
5 | import CardBody from 'wcstripe/settings/card-body';
6 | const PaymentRequestsEnableSection = () => {
7 | const [
8 | isPaymentRequestEnabled,
9 | updateIsPaymentRequestEnabled,
10 | ] = usePaymentRequestEnabledSettings();
11 |
12 | return (
13 |
14 |
15 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default PaymentRequestsEnableSection;
34 |
--------------------------------------------------------------------------------
/client/entrypoints/payment-request-settings/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .payment-method-settings {
4 | &__breadcrumbs {
5 | margin-bottom: 40px;
6 | }
7 |
8 | &__option-muted-text {
9 | color: #757575;
10 | }
11 |
12 | &__option-help-text {
13 | color: #757575;
14 | padding-left: 30px;
15 |
16 | @media ( min-width: 600px ) {
17 | padding-left: 26px;
18 | }
19 | }
20 |
21 | &__cta-selection {
22 | .components-base-control__label {
23 | // hack until the `hideLabelFromVision` is fixed for radio controls in Gutenberg
24 | display: none;
25 | }
26 | }
27 |
28 | &__preview {
29 | border: 1px dashed #ddd;
30 | box-sizing: border-box;
31 | border-radius: 3px;
32 | background-color: #f0f0f0;
33 | padding: 16px;
34 | }
35 |
36 | &__preview-help-text {
37 | font-style: italic;
38 | }
39 | }
40 |
41 | .components-radio-control__input[type='radio']:checked::before {
42 | box-sizing: border-box;
43 | }
44 |
45 | .payment-request-settings {
46 | &__location {
47 | margin-bottom: 26px !important;
48 | }
49 | }
50 |
51 | .express-checkout-settings {
52 | .components-card-body {
53 | padding: calc(16px) calc(24px);
54 |
55 | @include breakpoint( '>660px' ) {
56 | padding: 24px 24px 24px 24px;
57 | }
58 | }
59 |
60 | &__icon {
61 | border-radius: 2px;
62 | display: none;
63 | height: 40px;
64 | margin: 1px 17px 1px 1px; // 1px to accommodate for box-shadow
65 | box-shadow: 0 0 0 1px #ddd;
66 | background: #fff;
67 |
68 | @include breakpoint( '>660px' ) {
69 | display: inline-block;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/client/entrypoints/payment-request-settings/utils/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Whether or not to use Google Pay branded button in Chrome.
3 | *
4 | * @return {boolean} Use Google Pay button in Chrome.
5 | */
6 | export const shouldUseGooglePayBrand = () => {
7 | const ua = window.navigator.userAgent.toLowerCase();
8 | const isChrome =
9 | /chrome/.test( ua ) &&
10 | ! /edge|edg|opr|brave\//.test( ua ) &&
11 | window.navigator.vendor === 'Google Inc.';
12 | // newer versions of Brave do not have the userAgent string
13 | const isBrave = isChrome && window.navigator.brave;
14 | return isChrome && ! isBrave;
15 | };
16 |
--------------------------------------------------------------------------------
/client/express-checkout/compatibility/__tests__/wc-order-attribution.test.js:
--------------------------------------------------------------------------------
1 | import { applyFilters } from '@wordpress/hooks';
2 |
3 | describe( 'ECE order attribution compatibility', () => {
4 | it( 'filters out order attribution data', () => {
5 | const orderAttributionData = applyFilters(
6 | 'wcstripe.express-checkout.cart-place-order-extension-data',
7 | {
8 | session_count: '1',
9 | session_pages: '79',
10 | session_start_time: '2025-01-14 14:50:29',
11 | source_type: 'utm',
12 | user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
13 | utm_source: 'Facebook',
14 | }
15 | );
16 |
17 | expect( orderAttributionData ).toStrictEqual( {
18 | session_count: '1',
19 | session_pages: '79',
20 | session_start_time: '2025-01-14 14:50:29',
21 | source_type: 'utm',
22 | user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
23 | utm_source: 'Facebook',
24 | } );
25 | } );
26 | } );
27 |
--------------------------------------------------------------------------------
/client/express-checkout/compatibility/__tests__/wc-product-page.test.js:
--------------------------------------------------------------------------------
1 | import { applyFilters } from '@wordpress/hooks';
2 | import { render } from '@testing-library/react';
3 | import 'wcstripe/express-checkout/compatibility/wc-product-page';
4 |
5 | describe( 'ECE product page compatibility', () => {
6 | describe( 'filters out data when adding item to the cart', () => {
7 | it( 'single variation form is present', () => {
8 | function App() {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 | render( );
16 |
17 | const cartAddItemData = applyFilters(
18 | 'wcstripe.express-checkout.cart-add-item',
19 | {}
20 | );
21 |
22 | expect( cartAddItemData ).toStrictEqual( { id: 123 } );
23 | } );
24 | } );
25 | } );
26 |
--------------------------------------------------------------------------------
/client/express-checkout/compatibility/wc-order-attribution.js:
--------------------------------------------------------------------------------
1 | import { addFilter } from '@wordpress/hooks';
2 | import { extractOrderAttributionData } from 'wcstripe/blocks/utils';
3 |
4 | addFilter(
5 | 'wcstripe.express-checkout.cart-place-order-extension-data',
6 | 'automattic/wcstripe/express-checkout',
7 | ( extensionData ) => {
8 | const orderAttributionData = {};
9 | for ( const [ name, value ] of Object.entries(
10 | extractOrderAttributionData()
11 | ) ) {
12 | if ( name && value ) {
13 | orderAttributionData[
14 | name.replace( 'wc_order_attribution_', '' )
15 | ] = value;
16 | }
17 | }
18 | return {
19 | ...extensionData,
20 | 'woocommerce/order-attribution': orderAttributionData,
21 | };
22 | }
23 | );
24 |
--------------------------------------------------------------------------------
/client/express-checkout/compatibility/wc-product-page.js:
--------------------------------------------------------------------------------
1 | import jQuery from 'jquery';
2 | import { addFilter } from '@wordpress/hooks';
3 |
4 | /**
5 | * Sets the product ID when using the BlocksAPI and single variation form is present.
6 | */
7 | addFilter(
8 | 'wcstripe.express-checkout.cart-add-item',
9 | 'automattic/wcstripe/express-checkout',
10 | ( productData ) => {
11 | const $variationInformation = jQuery( '.single_variation_wrap' );
12 | if ( ! $variationInformation.length ) {
13 | return productData;
14 | }
15 |
16 | const productId = $variationInformation
17 | .find( 'input[name="product_id"]' )
18 | .val();
19 | return {
20 | ...productData,
21 | id: parseInt( productId, 10 ),
22 | };
23 | }
24 | );
25 |
--------------------------------------------------------------------------------
/client/hooks/use-toggle.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 |
3 | const useToggle = ( initialValue = false ) => {
4 | const [ value, setValue ] = useState( initialValue );
5 | const toggleValue = useCallback(
6 | () => setValue( ( oldValue ) => ! oldValue ),
7 | [ setValue ]
8 | );
9 |
10 | return [ value, toggleValue ];
11 | };
12 |
13 | export default useToggle;
14 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/40a58f52457c4037ec02758e7ed8cf7dfacd4c75/client/index.js
--------------------------------------------------------------------------------
/client/optimized-checkout/__tests__/apply-styles.test.js:
--------------------------------------------------------------------------------
1 | import { applyStyles } from 'wcstripe/optimized-checkout/apply-styles';
2 |
3 | describe( 'applyStyles', () => {
4 | it( 'Correctly apply the required styles to HTML elements', () => {
5 | document.body.innerHTML = `
6 |
9 |
10 |
11 |
12 |
13 |
14 | `;
15 |
16 | applyStyles();
17 |
18 | const paymentMethodOptions = document.querySelectorAll(
19 | 'input[name=radio-control-wc-payment-method-options]'
20 | );
21 | expect( paymentMethodOptions.length ).toBe( 1 );
22 |
23 | const stripeContent = document.getElementById(
24 | 'radio-control-wc-payment-method-options-stripe__content'
25 | );
26 | expect(
27 | stripeContent.classList.contains( 'optimized-checkout-element' )
28 | ).toBe( true );
29 |
30 | const stripeLabel = document.getElementById(
31 | 'radio-control-wc-payment-method-options-stripe__label'
32 | );
33 | expect(
34 | stripeLabel.classList.contains( 'optimized-checkout-element' )
35 | ).toBe( true );
36 |
37 | const stripeIframe = document.querySelector(
38 | '.wcstripe-payment-element iframe'
39 | );
40 | expect( stripeIframe.style.margin ).toBe( '0px' );
41 | } );
42 | } );
43 |
--------------------------------------------------------------------------------
/client/optimized-checkout/__tests__/handle-display-of-payment-instructions.test.js:
--------------------------------------------------------------------------------
1 | import { handleDisplayOfPaymentInstructions } from 'wcstripe/optimized-checkout/handle-display-of-payment-instructions';
2 |
3 | describe( 'handleDisplayOfPaymentInstructions', () => {
4 | document.body.innerHTML = `
5 |
6 |
7 | `;
8 |
9 | const cardInstructions = document.getElementById(
10 | 'wc-stripe-payment-method-instructions-card'
11 | );
12 | const sepaDebitInstructions = document.getElementById(
13 | 'wc-stripe-payment-method-instructions-sepa_debit'
14 | );
15 |
16 | it( 'Correctly handles the display of payment method instructions (Card)', () => {
17 | handleDisplayOfPaymentInstructions( 'card' );
18 |
19 | expect( cardInstructions.style.display ).toBe( 'block' );
20 | expect( sepaDebitInstructions.style.display ).toBe( 'none' );
21 | } );
22 | it( 'Correctly handles the display of payment method instructions (SEPA Debit)', () => {
23 | handleDisplayOfPaymentInstructions( 'sepa_debit' );
24 |
25 | expect( sepaDebitInstructions.style.display ).toBe( 'block' );
26 | expect( cardInstructions.style.display ).toBe( 'none' );
27 | } );
28 | } );
29 |
--------------------------------------------------------------------------------
/client/optimized-checkout/apply-styles.js:
--------------------------------------------------------------------------------
1 | const OPTIMIZED_CHECKOUT_ELEMENT_CLASS = 'optimized-checkout-element';
2 |
3 | export const applyStyles = () => {
4 | // Add the optimized checkout element class to the Stripe payment method elements.
5 | document
6 | .getElementById(
7 | 'radio-control-wc-payment-method-options-stripe__content'
8 | )
9 | .classList.add( OPTIMIZED_CHECKOUT_ELEMENT_CLASS );
10 | document
11 | .getElementById(
12 | 'radio-control-wc-payment-method-options-stripe__label'
13 | )
14 | .classList.add( OPTIMIZED_CHECKOUT_ELEMENT_CLASS );
15 |
16 | // Style the Stripe iframe to remove the margin and set width to 100%.
17 | const stripeIframe = document.querySelector(
18 | '.wcstripe-payment-element iframe'
19 | );
20 | stripeIframe.style.margin = 0;
21 | stripeIframe.style.width = '100%';
22 | };
23 |
--------------------------------------------------------------------------------
/client/optimized-checkout/handle-display-of-payment-instructions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Handle display of payment method instructions
3 | *
4 | * @param {string} method The payment method name.
5 | */
6 | export const handleDisplayOfPaymentInstructions = ( method ) => {
7 | document
8 | .querySelectorAll( '.wc-stripe-payment-method-instruction' )
9 | ?.forEach( ( element ) => {
10 | element.style.display = 'none';
11 | } );
12 | const currentInstructionsDiv = document.getElementById(
13 | 'wc-stripe-payment-method-instructions-' + method
14 | );
15 | if ( currentInstructionsDiv ) {
16 | currentInstructionsDiv.style.display = 'block';
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/client/optimized-checkout/handle-display-of-saving-checkbox.js:
--------------------------------------------------------------------------------
1 | import { NON_REUSABLE_METHODS } from 'wcstripe/stripe-utils/constants';
2 | import { getStripeServerData } from 'wcstripe/stripe-utils';
3 |
4 | export const handleDisplayOfSavingCheckbox = ( method ) => {
5 | // For block checkout
6 | const saveCardInfoContainerBlocks = document.querySelector(
7 | '.wc-block-components-payment-methods__save-card-info'
8 | );
9 | if ( saveCardInfoContainerBlocks ) {
10 | saveCardInfoContainerBlocks.style.display = NON_REUSABLE_METHODS.includes(
11 | method
12 | )
13 | ? 'none'
14 | : 'block';
15 | return;
16 | }
17 |
18 | // For classic checkout
19 | const saveCardInfoContainerClassic = document.querySelector(
20 | '.woocommerce-SavedPaymentMethods-saveNew'
21 | );
22 | if ( saveCardInfoContainerClassic ) {
23 | const createAccountCheckbox = document.getElementById(
24 | 'createaccount'
25 | );
26 | const signupSelected =
27 | getStripeServerData()?.isSignupOnCheckoutAllowed &&
28 | createAccountCheckbox?.checked;
29 | const hasSavedPaymentMethodSelected =
30 | document.querySelector( 'input[name=wc-stripe-payment-token]' ) &&
31 | document.getElementById( 'wc-stripe-upe-form' )?.style.display ===
32 | 'none';
33 | if (
34 | ( getStripeServerData()?.isLoggedIn || signupSelected ) &&
35 | ! hasSavedPaymentMethodSelected &&
36 | ! NON_REUSABLE_METHODS.includes( method )
37 | ) {
38 | saveCardInfoContainerClassic.style.display = 'block';
39 | } else {
40 | saveCardInfoContainerClassic.style.display = 'none';
41 | }
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/client/payment-method-icons/affirm/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const AffirmIcon = ( props ) => ;
6 |
7 | export default AffirmIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/afterpay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const AfterpayIcon = ( props ) => ;
12 |
13 | export default AfterpayIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/alipay/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/payment-method-icons/alipay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding: 0;
8 | border: none;
9 |
10 | img {
11 | outline: 1px solid #ddd;
12 | }
13 | `;
14 |
15 | const AlipayIcon = ( props ) => ;
16 |
17 | export default AlipayIcon;
18 |
--------------------------------------------------------------------------------
/client/payment-method-icons/amazon-pay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseIcon from '../styles/base-icon';
3 | import icon from './icon.svg';
4 |
5 | const AmazonPayIcon = ( props ) => ;
6 |
7 | export default AmazonPayIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/apple-pay/icon-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/payment-method-icons/apple-pay/icon-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/payment-method-icons/apple-pay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon-black.svg';
4 |
5 | const ApplePayIcon = ( props ) => ;
6 |
7 | export default ApplePayIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/bacs-debit/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const BacsDebitIcon = ( props ) => ;
6 |
7 | export default BacsDebitIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/bancontact/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const BancontactIcon = ( props ) => ;
6 |
7 | export default BancontactIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/bank-debit/icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/payment-method-icons/bank-debit/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const BankDebitIcon = ( props ) => ;
6 |
7 | export default BankDebitIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/blik/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | background: #010101;
8 | `;
9 |
10 | const BlikIcon = ( props ) => ;
11 |
12 | export default BlikIcon;
13 |
--------------------------------------------------------------------------------
/client/payment-method-icons/boleto/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/payment-method-icons/boleto/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const BoletoIcon = ( props ) => ;
12 |
13 | export default BoletoIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/cards/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseIcon from '../styles/base-icon';
3 | import icon from './icon.svg';
4 |
5 | const CardsIcon = ( props ) => ;
6 |
7 | export default CardsIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/cashapp/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | background: #00d650;
8 | `;
9 |
10 | const CashAppIcon = ( props ) => ;
11 |
12 | export default CashAppIcon;
13 |
--------------------------------------------------------------------------------
/client/payment-method-icons/clearpay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const ClearpayIcon = ( props ) => ;
12 |
13 | export default ClearpayIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/eps/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/payment-method-icons/eps/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const EpsIcon = ( props ) => ;
6 |
7 | export default EpsIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/giropay/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/payment-method-icons/giropay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const GiropayIcon = ( props ) => ;
6 |
7 | export default GiropayIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/google-pay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const GooglePayIcon = ( props ) => ;
6 |
7 | export default GooglePayIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/ideal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconWithShell from '../styles/icon-with-shell';
3 | import icon from './icon.svg';
4 |
5 | const IdealIcon = ( props ) => ;
6 |
7 | export default IdealIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/index.js:
--------------------------------------------------------------------------------
1 | import AlipayIcon from './alipay';
2 | import BlikIcon from './blik';
3 | import CreditCardIcon from './cards';
4 | import GiropayIcon from './giropay';
5 | import KlarnaIcon from './klarna';
6 | import AffirmIcon from './affirm';
7 | import AfterpayIcon from './afterpay';
8 | import ClearpayIcon from './clearpay';
9 | import MultibancoIcon from './multibanco';
10 | import SofortIcon from './sofort';
11 | import SepaIcon from './sepa';
12 | import EpsIcon from './eps';
13 | import BancontactIcon from './bancontact';
14 | import IdealIcon from './ideal';
15 | import P24Icon from './p24';
16 | import BoletoIcon from './boleto';
17 | import OxxoIcon from './oxxo';
18 | import WechatPayIcon from './wechat-pay';
19 | import CashAppIcon from './cashapp';
20 | import BankDebitIcon from './bank-debit';
21 | import BacsDebitIcon from './bacs-debit';
22 | import StripeIcon from './stripe';
23 |
24 | export default {
25 | alipay: AlipayIcon,
26 | blik: BlikIcon,
27 | card: CreditCardIcon,
28 | giropay: GiropayIcon,
29 | klarna: KlarnaIcon,
30 | affirm: AffirmIcon,
31 | afterpay: AfterpayIcon,
32 | clearpay: ClearpayIcon,
33 | multibanco: MultibancoIcon,
34 | sepa_debit: SepaIcon,
35 | sofort: SofortIcon,
36 | eps: EpsIcon,
37 | bancontact: BancontactIcon,
38 | ideal: IdealIcon,
39 | p24: P24Icon,
40 | boleto: BoletoIcon,
41 | oxxo: OxxoIcon,
42 | wechat_pay: WechatPayIcon,
43 | cashapp: CashAppIcon,
44 | us_bank_account: BankDebitIcon,
45 | bacs_debit: BacsDebitIcon,
46 | acss_debit: BankDebitIcon,
47 | au_becs_debit: BankDebitIcon,
48 | stripe: StripeIcon,
49 | };
50 |
--------------------------------------------------------------------------------
/client/payment-method-icons/klarna/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding: 0;
8 | border: none;
9 |
10 | img {
11 | outline: 1px solid #ddd;
12 | }
13 | `;
14 |
15 | const KlarnaIcon = ( props ) => ;
16 |
17 | export default KlarnaIcon;
18 |
--------------------------------------------------------------------------------
/client/payment-method-icons/link/icon-black.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/client/payment-method-icons/link/icon.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/client/payment-method-icons/link/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseIcon from '../styles/base-icon';
3 | import icon from './icon.svg';
4 |
5 | const LinkIcon = ( props ) => ;
6 |
7 | export default LinkIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/multibanco/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const MultibancoIcon = ( props ) => ;
12 |
13 | export default MultibancoIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/oxxo/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/payment-method-icons/oxxo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const OxxoIcon = ( props ) => ;
12 |
13 | export default OxxoIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/p24/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 0;
8 | padding-bottom: 0;
9 | `;
10 |
11 | const P24Icon = ( props ) => ;
12 |
13 | export default P24Icon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/payment-request/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseIcon from '../styles/base-icon';
3 | import icon from './icon.svg';
4 |
5 | const PaymentRequestIcon = ( props ) => ;
6 |
7 | export default PaymentRequestIcon;
8 |
--------------------------------------------------------------------------------
/client/payment-method-icons/sepa/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | background: #10298e;
8 | `;
9 |
10 | const SepaIcon = ( props ) => ;
11 |
12 | export default SepaIcon;
13 |
--------------------------------------------------------------------------------
/client/payment-method-icons/sofort/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | background: #ef809f;
8 | `;
9 |
10 | const SofortIcon = ( props ) => ;
11 |
12 | export default SofortIcon;
13 |
--------------------------------------------------------------------------------
/client/payment-method-icons/stripe/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/payment-method-icons/stripe/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const StripeIcon = ( props ) => ;
12 |
13 | export default StripeIcon;
14 |
--------------------------------------------------------------------------------
/client/payment-method-icons/styles/base-icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { css } from '@emotion/react';
4 |
5 | const IconSizesMap = {
6 | small: css`
7 | height: 24px;
8 | width: 37px;
9 | `,
10 | medium: css`
11 | height: 40px;
12 | width: 65px;
13 | `,
14 | };
15 |
16 | const Wrapper = styled.span`
17 | // also accounts for null size
18 | ${ ( { size } ) => IconSizesMap[ size ] || '' }
19 |
20 | box-sizing: border-box;
21 | display: inline-flex;
22 | justify-content: center;
23 |
24 | // most icons need a border. Ensuring that the border is part of the icon's size allows for consistent spacing.
25 | // in this case, the icon's border is just transparent (but it's still part of the icon's size).
26 | border: 1px solid transparent;
27 |
28 | img {
29 | max-width: 100%;
30 | }
31 | `;
32 |
33 | const BaseIcon = ( { src, children, alt, size = 'small', ...restProps } ) => (
34 |
35 | { src ?
: children }
36 |
37 | );
38 |
39 | export default BaseIcon;
40 |
--------------------------------------------------------------------------------
/client/payment-method-icons/styles/icon-with-shell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { css } from '@emotion/react';
4 | import BaseIcon from './base-icon';
5 |
6 | const IconSpacingMap = {
7 | small: css`
8 | padding: 4px;
9 | `,
10 | medium: css`
11 | padding: 7px;
12 | `,
13 | };
14 |
15 | const Wrapper = styled( BaseIcon )`
16 | background: white;
17 | border-color: #ddd;
18 | border-radius: 5px;
19 | overflow: hidden;
20 |
21 | ${ ( { size } ) => IconSpacingMap[ size ] || '' }
22 | `;
23 |
24 | const IconWithShell = ( { size = 'small', ...restProps } ) => (
25 |
26 | );
27 |
28 | export default IconWithShell;
29 |
--------------------------------------------------------------------------------
/client/payment-method-icons/wechat-pay/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/payment-method-icons/wechat-pay/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import IconWithShell from '../styles/icon-with-shell';
4 | import icon from './icon.svg';
5 |
6 | const Wrapper = styled( IconWithShell )`
7 | padding-top: 4px;
8 | padding-bottom: 4px;
9 | `;
10 |
11 | const WechatPayIcon = ( props ) => ;
12 |
13 | export default WechatPayIcon;
14 |
--------------------------------------------------------------------------------
/client/settings/advanced-settings-section/__tests__/single-payment-element-feature.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import React, { useContext } from 'react';
3 | import userEvent from '@testing-library/user-event';
4 | import SinglePaymentElementFeature from 'wcstripe/settings/advanced-settings-section/single-payment-element-feature';
5 | import UpeToggleContext from 'wcstripe/settings/upe-toggle/context';
6 |
7 | jest.useFakeTimers();
8 |
9 | describe( 'Single Payment Element feature setting', () => {
10 | it( 'should render', () => {
11 | render( );
12 |
13 | expect(
14 | screen.queryByText(
15 | 'Enable Optimized Checkout Suite (recommended)'
16 | )
17 | ).toBeInTheDocument();
18 | } );
19 |
20 | it( 'should be disabled when UPE is disabled', () => {
21 | const UpdateUpeDisabledFlagMock = () => {
22 | const { setIsUpeEnabled } = useContext( UpeToggleContext );
23 | setTimeout( () => {
24 | setIsUpeEnabled( false );
25 | }, 1000 );
26 | return null;
27 | };
28 |
29 | render(
30 |
31 |
32 |
33 |
34 | );
35 |
36 | const checkbox = screen.getByTestId(
37 | 'single-payment-element-checkbox'
38 | );
39 |
40 | userEvent.click( checkbox );
41 |
42 | jest.runAllTimers();
43 |
44 | expect( checkbox ).toBeDisabled();
45 | expect( checkbox ).not.toBeChecked();
46 | } );
47 | } );
48 |
--------------------------------------------------------------------------------
/client/settings/advanced-settings-section/debug-mode.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { CheckboxControl } from '@wordpress/components';
4 | import { useDebugLog } from 'wcstripe/data';
5 |
6 | const DebugMode = () => {
7 | const [ isLoggingChecked, setIsLoggingChecked ] = useDebugLog();
8 |
9 | return (
10 | <>
11 | { __( 'Debug mode', 'woocommerce-gateway-stripe' ) }
12 | Status > Logs.',
20 | 'woocommerce-gateway-stripe'
21 | ) }
22 | checked={ isLoggingChecked }
23 | onChange={ setIsLoggingChecked }
24 | />
25 | >
26 | );
27 | };
28 |
29 | export default DebugMode;
30 |
--------------------------------------------------------------------------------
/client/settings/advanced-settings-section/index.js:
--------------------------------------------------------------------------------
1 | /* global wc_stripe_settings_params */
2 | import { __ } from '@wordpress/i18n';
3 | import React from 'react';
4 | import { Card } from '@wordpress/components';
5 | import SettingsSection from '../settings-section';
6 | import CardBody from '../card-body';
7 | import DebugMode from './debug-mode';
8 | import ExperimentalFeatures from './experimental-features';
9 | import LoadableSettingsSection from 'wcstripe/settings/loadable-settings-section';
10 | import SinglePaymentElementFeature from 'wcstripe/settings/advanced-settings-section/single-payment-element-feature';
11 |
12 | const AdvancedSettingsDescription = () => (
13 | <>
14 | { __( 'Advanced settings', 'woocommerce-gateway-stripe' ) }
15 |
16 | { __(
17 | 'Enable and configure advanced features for your store.',
18 | 'woocommerce-gateway-stripe'
19 | ) }
20 |
21 | >
22 | );
23 |
24 | const AdvancedSettings = () => {
25 | const isOcAvailable = wc_stripe_settings_params.is_oc_available; // eslint-disable-line camelcase
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 | { isOcAvailable && }
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default AdvancedSettings;
42 |
--------------------------------------------------------------------------------
/client/settings/card-body.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { CardBody } from '@wordpress/components';
3 |
4 | export default styled( CardBody )`
5 | // increasing the specificity of the styles to override the Gutenberg ones
6 | &.is-size-medium.is-size-medium {
7 | padding: 24px;
8 | }
9 |
10 | h4 {
11 | margin-top: 0;
12 | margin-bottom: 1em;
13 | }
14 |
15 | > * {
16 | margin-top: 0;
17 | margin-bottom: 1em;
18 |
19 | // fixing the spacing on the inputs and their help text, to ensure it is consistent
20 | &:last-child {
21 | margin-bottom: 0;
22 |
23 | > :last-child {
24 | margin-bottom: 0;
25 | }
26 | }
27 | }
28 |
29 | input,
30 | select {
31 | margin: 0;
32 | }
33 |
34 | // spacing adjustment on "Express checkouts > Show express checkouts on" list
35 | ul > li:last-child {
36 | margin-bottom: 0;
37 |
38 | .components-base-control__field {
39 | margin-bottom: 0;
40 | }
41 | }
42 |
43 | // spacing in the "Express checkouts" settings page
44 | .components-radio-control__option {
45 | margin-bottom: 8px;
46 | }
47 |
48 | .components-base-control__help {
49 | margin-top: unset;
50 | }
51 | `;
52 |
--------------------------------------------------------------------------------
/client/settings/card-footer.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { CardFooter } from '@wordpress/components';
3 |
4 | export default styled( CardFooter )`
5 | // increasing the specificity of the styles to override the Gutenberg ones
6 | &.is-size-medium.is-size-medium {
7 | padding: 24px;
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/client/settings/general-settings-section/__tests__/remove-method-confirmation-modal.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { screen, render } from '@testing-library/react';
3 | import userEvent from '@testing-library/user-event';
4 | import RemoveMethodConfirmationModal from '../remove-method-confirmation-modal';
5 |
6 | describe( 'RemoveMethodConfirmationModal', () => {
7 | const handleCloseMock = jest.fn();
8 | const handleRemoveMock = jest.fn();
9 |
10 | it( 'should render the information', () => {
11 | render(
12 |
17 | );
18 |
19 | expect(
20 | screen.queryByRole( 'heading', {
21 | name: 'Remove giropay from checkout',
22 | } )
23 | ).toBeInTheDocument();
24 | } );
25 |
26 | it( 'should call onClose when the action is cancelled', () => {
27 | render(
28 |
33 | );
34 |
35 | expect( handleCloseMock ).not.toHaveBeenCalled();
36 |
37 | userEvent.click( screen.getByRole( 'button', { name: 'Cancel' } ) );
38 |
39 | expect( handleCloseMock ).toHaveBeenCalled();
40 | } );
41 |
42 | it( 'should call onConfirm when the action is confirmed', () => {
43 | render(
44 |
49 | );
50 |
51 | expect( handleRemoveMock ).not.toHaveBeenCalled();
52 |
53 | userEvent.click( screen.getByRole( 'button', { name: 'Remove' } ) );
54 |
55 | expect( handleRemoveMock ).toHaveBeenCalled();
56 | } );
57 | } );
58 |
--------------------------------------------------------------------------------
/client/settings/general-settings-section/section-footer.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { ExternalLink } from '@wordpress/components';
4 | import CardFooter from 'wcstripe/settings/card-footer';
5 | import PaymentMethodsUnavailableList from 'wcstripe/settings/general-settings-section/payment-methods-unavailable-list';
6 |
7 | const SectionFooter = () => (
8 |
9 |
10 |
14 | { __(
15 | 'Get more payment methods',
16 | 'woocommerce-gateway-stripe'
17 | ) }
18 |
19 |
20 |
21 |
22 | );
23 |
24 | export default SectionFooter;
25 |
--------------------------------------------------------------------------------
/client/settings/general-settings-section/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/breakpoints';
2 |
3 | .payment-methods {
4 | &__unavailable-methods {
5 | display: none;
6 | margin: 0 0 0 1.5rem;
7 |
8 | @include breakpoint( '>660px' ) {
9 | display: flex;
10 | }
11 | }
12 |
13 | &__unavailable-method {
14 | border-radius: 2px;
15 | width: 38px;
16 | height: 24px;
17 | margin: 1px 5px 1px 1px;
18 |
19 | &:last-child {
20 | margin-right: 1px;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/client/settings/loadable-account-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LoadableBlock } from '../components/loadable';
3 | import { useAccount } from 'wcstripe/data/account/hooks';
4 | import { useAccountKeys } from 'wcstripe/data/account-keys/hooks';
5 |
6 | const LoadableAccountSection = ( {
7 | children,
8 | numLines,
9 | keepContent = false,
10 | } ) => {
11 | const { isLoading: isAccountLoading } = useAccount();
12 | const { isLoading: areAccountKeysLoading } = useAccountKeys();
13 | let isLoading = areAccountKeysLoading || isAccountLoading;
14 |
15 | if ( keepContent ) {
16 | isLoading = false;
17 | }
18 |
19 | return (
20 |
21 | { children }
22 |
23 | );
24 | };
25 |
26 | export default LoadableAccountSection;
27 |
--------------------------------------------------------------------------------
/client/settings/loadable-payment-gateway-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { usePaymentGateway } from '../data';
3 | import { LoadableBlock } from '../components/loadable';
4 |
5 | const LoadablePaymentGatewaySection = ( { children, numLines } ) => {
6 | const { isLoading } = usePaymentGateway();
7 |
8 | return (
9 |
10 | { children }
11 |
12 | );
13 | };
14 |
15 | export default LoadablePaymentGatewaySection;
16 |
--------------------------------------------------------------------------------
/client/settings/loadable-settings-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSettings } from '../data';
3 | import { LoadableBlock } from '../components/loadable';
4 |
5 | const LoadableSettingsSection = ( { children, numLines } ) => {
6 | const { isLoading } = useSettings();
7 |
8 | return (
9 |
10 | { children }
11 |
12 | );
13 | };
14 |
15 | export default LoadableSettingsSection;
16 |
--------------------------------------------------------------------------------
/client/settings/payment-gateway-manager/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { getQuery } from '@woocommerce/navigation';
3 | import PaymentGatewayManager from '..';
4 |
5 | jest.mock( '@woocommerce/navigation', () => ( {
6 | getQuery: jest.fn().mockReturnValue( { section: 'sectionX' } ),
7 | } ) );
8 |
9 | jest.mock( '../constants', () => ( {
10 | gatewaysInfo: {
11 | sectionX: { title: 'Section X', geography: 'Brazil' },
12 | sectionY: { title: 'Section Y', geography: 'Italy' },
13 | },
14 | } ) );
15 |
16 | describe( 'PaymentGatewayManager', () => {
17 | afterEach( () => {
18 | jest.clearAllMocks();
19 | } );
20 |
21 | it( 'should render a title and geography when mounted', () => {
22 | render( );
23 |
24 | expect( screen.getByText( 'Section X' ) ).toBeInTheDocument();
25 | expect( screen.getByText( 'Brazil' ) ).toBeInTheDocument();
26 | } );
27 |
28 | it( 'should render the correct information based in the section', () => {
29 | getQuery.mockReturnValue( { section: 'sectionX' } );
30 | render( );
31 |
32 | expect( screen.getByText( 'Section X' ) ).toBeInTheDocument();
33 | expect( screen.queryByText( 'Section Y' ) ).not.toBeInTheDocument();
34 | } );
35 | } );
36 |
--------------------------------------------------------------------------------
/client/settings/payment-gateway-manager/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { ExternalLink } from '@wordpress/components';
4 | import { getQuery } from '@woocommerce/navigation';
5 | import SettingsLayout from '../settings-layout';
6 | import SettingsSection from '../settings-section';
7 | import PaymentGatewaySection from '../payment-gateway-section';
8 | import SavePaymentGatewaySection from '../save-payment-gateway-section';
9 | import { gatewaysInfo } from './constants';
10 |
11 | const GatewayDescription = () => {
12 | const { section } = getQuery();
13 | const info = gatewaysInfo[ section ];
14 | return (
15 | <>
16 | { info.title }
17 | { info.geography }
18 |
19 |
23 | { __(
24 | 'Activate in your Stripe Dashboard',
25 | 'woocommerce-gateway-stripe'
26 | ) }
27 |
28 |
29 |
30 |
31 | { __(
32 | 'Payment Method Guide',
33 | 'woocommerce-gateway-stripe'
34 | ) }
35 |
36 |
37 | >
38 | );
39 | };
40 |
41 | const PaymentGatewayManager = () => {
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default PaymentGatewayManager;
53 |
--------------------------------------------------------------------------------
/client/settings/payment-method-icon/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import paymentMethodsMap from '../../payment-methods-map';
3 | import './style.scss';
4 |
5 | const PaymentMethodIcon = ( { name, showName } ) => {
6 | const paymentMethod = paymentMethodsMap[ name ];
7 |
8 | if ( ! paymentMethod ) {
9 | return <>>;
10 | }
11 |
12 | const { label, Icon } = paymentMethod;
13 |
14 | return (
15 |
16 |
20 | { showName && (
21 |
22 | { label }
23 |
24 | ) }
25 |
26 | );
27 | };
28 |
29 | export default PaymentMethodIcon;
30 |
--------------------------------------------------------------------------------
/client/settings/payment-method-icon/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .woocommerce-gateway-stripe__payment-method-icon {
4 | display: inline-flex;
5 | align-items: center;
6 | vertical-align: middle;
7 | max-width: 160px;
8 |
9 | @include breakpoint( '>660px' ) {
10 | max-width: inherit;
11 | }
12 |
13 | &__label {
14 | margin-left: 4px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/client/settings/payment-request-section/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .express-checkouts {
4 | .express-checkouts-list {
5 | .express-checkout {
6 | display: flex;
7 | margin: 0;
8 | padding: 16px 24px 14px 24px;
9 | flex-wrap: wrap;
10 | justify-content: flex-start;
11 | align-items: center;
12 |
13 | @include breakpoint( '>660px' ) {
14 | padding: 24px 24px 24px 24px;
15 | flex-wrap: nowrap;
16 | }
17 |
18 | &:not( :last-child ) {
19 | box-shadow: inset 0 -1px 0 #e8eaeb;
20 | }
21 |
22 | &__checkbox {
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 |
27 | .components-base-control__field {
28 | margin: 0 4px 0 0;
29 | }
30 | }
31 |
32 | &__icon {
33 | border-radius: 2px;
34 | display: none;
35 | flex: 0 0 63.69px;
36 | height: 40px;
37 | margin: 1px 17px 1px 1px; // 1px to accommodate for box-shadow
38 | box-shadow: 0 0 0 1px #ddd;
39 |
40 | @include breakpoint( '>660px' ) {
41 | display: flex;
42 | }
43 | }
44 |
45 | &__label {
46 | display: inline-block;
47 | font-size: 14px;
48 | font-weight: 600;
49 | line-height: 20px;
50 | color: $gray-900;
51 | margin-bottom: 4px;
52 | }
53 |
54 | &__link {
55 | margin-left: auto;
56 | padding-left: 16px;
57 |
58 | a {
59 | white-space: nowrap;
60 | }
61 | }
62 |
63 | &__label-container {
64 | flex: 1;
65 | }
66 |
67 | .components-checkbox-control__label {
68 | display: none;
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/client/settings/payment-settings/constants.js:
--------------------------------------------------------------------------------
1 | export const RECONNECT_BANNER = 'stripe-reconnect';
2 | export const NEW_CHECKOUT_EXPERIENCE_BANNER = 'new-checkout-experience';
3 | export const NEW_CHECKOUT_EXPERIENCE_APMS_BANNER =
4 | 'new-checkout-experience-apms';
5 | export const BNPL_PROMOTION_BANNER = 'bnpl_promotion_banner';
6 |
--------------------------------------------------------------------------------
/client/settings/payment-settings/promotional-banner/banner-layout.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { Button, Card } from '@wordpress/components';
3 | import Pill from 'wcstripe/components/pill';
4 |
5 | export const BannerCard = styled( Card )`
6 | margin-bottom: 12px;
7 | `;
8 |
9 | export const BannerIllustration = styled.img`
10 | margin: 24px 0 0 24px;
11 | `;
12 |
13 | export const ButtonsRow = styled.p`
14 | margin: 0;
15 | `;
16 |
17 | export const CardInner = styled.div`
18 | display: flex;
19 | align-items: center;
20 | padding-bottom: 0;
21 | margin-bottom: 0;
22 | p {
23 | color: #757575;
24 | }
25 | @media ( max-width: 599px ) {
26 | display: block;
27 | }
28 | `;
29 |
30 | export const CardColumn = styled.div`
31 | flex: 1 auto;
32 | `;
33 |
34 | export const MainCTALink = styled( Button )`
35 | margin-right: 8px;
36 | `;
37 |
38 | export const NewPill = styled( Pill )`
39 | border-color: #674399;
40 | color: #674399;
41 | margin-bottom: 13px;
42 | `;
43 |
44 | export const DismissButton = styled( Button )`
45 | box-shadow: none !important;
46 | color: #757575 !important;
47 | `;
48 |
--------------------------------------------------------------------------------
/client/settings/payment-settings/promotional-banner/illustrations/default.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/settings/payment-settings/promotional-banner/illustrations/reconnect.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/client/settings/payment-settings/style.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .wcstripe-modal-button-group-container {
4 | display: flex;
5 | gap: $grid-unit-20;
6 | }
7 |
8 | .account-details {
9 | &__header {
10 | justify-content: space-between;
11 |
12 | > :first-child {
13 | display: flex;
14 | align-items: center;
15 |
16 | > * {
17 | &:not( :last-child ) {
18 | margin-right: 4px;
19 | }
20 | }
21 | }
22 |
23 | .components-dropdown-menu__toggle.has-icon {
24 | padding: 0;
25 | min-width: unset;
26 | }
27 |
28 | button.components-dropdown-menu__menu-item:last-of-type {
29 | color: rgb( 220, 30, 30 );
30 | }
31 | }
32 |
33 | &__heading {
34 | margin: 0;
35 | font-size: 16px;
36 | display: flex;
37 | align-items: center;
38 | flex-wrap: wrap;
39 | line-height: 2em;
40 |
41 | > * {
42 | &:not( :last-child ) {
43 | margin-right: 4px;
44 | }
45 | }
46 | }
47 | }
48 |
49 | .components-modal__screen-overlay {
50 | // This value should be lower than .woocommerce-transient-notices's z-index
51 | // to be able to see snackbar's messages
52 | z-index: 99998;
53 | }
54 |
55 | .woocommerce-stripe-connection__actions-wrapper {
56 | display: flex;
57 | flex-direction: row;
58 | align-items: center;
59 | gap: $grid-unit-20;
60 | }
61 |
--------------------------------------------------------------------------------
/client/settings/payment-settings/test-mode-checkbox.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import { React } from 'react';
3 | import { CheckboxControl } from '@wordpress/components';
4 | import interpolateComponents from 'interpolate-components';
5 | import { useTestMode } from 'wcstripe/data';
6 |
7 | const TestModeCheckbox = () => {
8 | const [ isTestModeEnabled, setTestMode ] = useTestMode();
9 |
10 | const handleCheckboxChange = ( isChecked ) => {
11 | setTestMode( isChecked );
12 | };
13 |
14 | return (
15 | <>
16 | { __( 'Test mode', 'woocommerce-gateway-stripe' ) }
17 |
30 | ),
31 | learnMoreLink: (
32 | // eslint-disable-next-line jsx-a11y/anchor-has-content
33 |
34 | ),
35 | },
36 | } ) }
37 | />
38 | >
39 | );
40 | };
41 |
42 | export default TestModeCheckbox;
43 |
--------------------------------------------------------------------------------
/client/settings/payments-and-transactions-section/statement-preview/icons/bank.js:
--------------------------------------------------------------------------------
1 | export const BankIcon = (
2 |
31 | );
32 |
--------------------------------------------------------------------------------
/client/settings/payments-and-transactions-section/statement-preview/icons/creditCard.js:
--------------------------------------------------------------------------------
1 | export const CreditCardIcon = (
2 |
31 | );
32 |
--------------------------------------------------------------------------------
/client/settings/payments-and-transactions-section/statement-preview/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { Icon } from '@wordpress/components';
4 | import CurrencyFactory from '@woocommerce/currency';
5 | import './style.scss';
6 | import { CreditCardIcon } from './icons/creditCard';
7 | import { CashAppIcon } from './icons/cashApp.js';
8 | import { BankIcon } from './icons/bank.js';
9 |
10 | const icons = {
11 | creditCard: CreditCardIcon,
12 | cashApp: CashAppIcon,
13 | bank: BankIcon,
14 | };
15 |
16 | const StatementPreview = ( { title, icon, text, className = '' } ) => {
17 | const StatementIcon = ( { icon: thisIcon } ) => {
18 | const iconSVG = icons?.[ thisIcon ] ? icons[ thisIcon ] : icons.bank;
19 | return ;
20 | };
21 |
22 | const currencySettings = CurrencyFactory( {
23 | symbol: '$',
24 | symbolPosition: 'left',
25 | precision: 2,
26 | decimalSeparator: '.',
27 | ...window?.wcSettings?.currency,
28 | } );
29 |
30 | // Handles formatting the preview amount according to the store's currency settings.
31 | const transactionAmount = currencySettings.formatAmount( 20 );
32 |
33 | return (
34 |
35 |
38 |
{ __( 'Transaction', 'woocommerce-gateway-stripe' ) }
39 |
{ __( 'Amount', 'woocommerce-gateway-stripe' ) }
40 |
41 |
{ text }
42 |
43 | { transactionAmount }
44 |
45 |
46 | );
47 | };
48 |
49 | export default StatementPreview;
50 |
--------------------------------------------------------------------------------
/client/settings/payments-and-transactions-section/statement-previews-wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 |
4 | const StatementPreviews = styled.div`
5 | display: grid;
6 | grid-template-columns: 1fr 1fr ${ ( props ) =>
7 | props.withTreeColumns ? ' 1fr' : '' };
8 | grid-column-gap: 15px;
9 | padding-bottom: 8px;
10 |
11 | > p {
12 | grid-column: 1/-1;
13 | margin-bottom: 8px;
14 | }
15 |
16 | @media screen and ( max-width: 609px ) {
17 | grid-template-columns: 1fr;
18 | }
19 |
20 | @media screen and ( min-width: 800px ) and ( max-width: 1109px ) {
21 | grid-template-columns: 1fr;
22 | }
23 | `;
24 |
25 | const StatementPreviewsWrapper = ( { withTreeColumns, children } ) => (
26 |
27 | Preview
28 | { children }
29 |
30 | );
31 |
32 | export default StatementPreviewsWrapper;
33 |
--------------------------------------------------------------------------------
/client/settings/save-payment-gateway-section/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { Button } from '@wordpress/components';
4 | import styled from '@emotion/styled';
5 | import SettingsSection from '../settings-section';
6 | import { usePaymentGateway } from '../../data';
7 |
8 | const SaveSectionWrapper = styled( SettingsSection )`
9 | text-align: right;
10 | `;
11 |
12 | const SavePaymentGatewaySection = () => {
13 | const { savePaymentGateway, isSaving, isLoading } = usePaymentGateway();
14 |
15 | return (
16 |
17 |
25 |
26 | );
27 | };
28 |
29 | export default SavePaymentGatewaySection;
30 |
--------------------------------------------------------------------------------
/client/settings/save-settings-section/__tests__/save-settings-section.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { screen, render } from '@testing-library/react';
3 | import userEvent from '@testing-library/user-event';
4 | import SaveSettingsSection from '..';
5 | import { useSettings } from 'wcstripe/data';
6 |
7 | jest.mock( 'wcstripe/data', () => ( {
8 | useSettings: jest.fn().mockReturnValue( {} ),
9 | } ) );
10 |
11 | describe( 'SaveSettingsSection', () => {
12 | it( 'should render the save button', () => {
13 | render( );
14 |
15 | expect( screen.queryByText( 'Save changes' ) ).toBeInTheDocument();
16 | } );
17 |
18 | it( 'disables the button when loading data', () => {
19 | useSettings.mockReturnValue( {
20 | isLoading: true,
21 | } );
22 |
23 | render( );
24 |
25 | expect( screen.getByText( 'Save changes' ) ).toBeDisabled();
26 | } );
27 |
28 | it( 'disables the button when saving data', () => {
29 | useSettings.mockReturnValue( {
30 | isSaving: true,
31 | } );
32 |
33 | render( );
34 |
35 | expect( screen.getByText( 'Save changes' ) ).toBeDisabled();
36 | } );
37 |
38 | it( 'calls `saveSettings` when the button is clicked', () => {
39 | const saveSettingsMock = jest.fn();
40 | useSettings.mockReturnValue( {
41 | isSaving: false,
42 | isLoading: false,
43 | saveSettings: saveSettingsMock,
44 | } );
45 |
46 | render( );
47 |
48 | const saveChangesButton = screen.getByText( 'Save changes' );
49 |
50 | expect( saveSettingsMock ).not.toHaveBeenCalled();
51 | expect( saveChangesButton ).not.toBeDisabled();
52 |
53 | userEvent.click( saveChangesButton );
54 |
55 | expect( saveSettingsMock ).toHaveBeenCalled();
56 | } );
57 | } );
58 |
--------------------------------------------------------------------------------
/client/settings/save-settings-section/index.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 | import React from 'react';
3 | import { Button } from '@wordpress/components';
4 | import styled from '@emotion/styled';
5 | import SettingsSection from '../settings-section';
6 | import { useSettings } from '../../data';
7 |
8 | const SaveSettingsSectionWrapper = styled( SettingsSection )`
9 | text-align: right;
10 | `;
11 |
12 | const SaveSettingsSection = ( { onSettingsSave } ) => {
13 | const { saveSettings, isSaving, isLoading } = useSettings();
14 |
15 | const onClickHandler = async () => {
16 | await saveSettings();
17 | if ( onSettingsSave ) {
18 | onSettingsSave();
19 | }
20 | };
21 |
22 | return (
23 |
24 |
32 |
33 | );
34 | };
35 |
36 | export default SaveSettingsSection;
37 |
--------------------------------------------------------------------------------
/client/settings/section-status/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Chip from 'wcstripe/components/chip';
3 |
4 | const SectionStatus = ( { isEnabled, children } ) => {
5 | return ;
6 | };
7 |
8 | export default SectionStatus;
9 |
--------------------------------------------------------------------------------
/client/settings/settings-layout.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 |
3 | const SettingsLayout = styled.div`
4 | margin: 24px auto 0;
5 | max-width: 1032px;
6 | display: flex;
7 | flex-flow: column;
8 |
9 | @media ( min-width: 960px ) {
10 | padding: 0 56px;
11 | }
12 | `;
13 |
14 | export default SettingsLayout;
15 |
--------------------------------------------------------------------------------
/client/settings/settings-section/__tests__/settings-section.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { screen, render } from '@testing-library/react';
3 | import SettingsSection from '..';
4 |
5 | describe( 'SettingsSection', () => {
6 | it( 'should render its children', () => {
7 | render(
8 |
9 | Children mock
10 |
11 | );
12 |
13 | expect( screen.queryByText( 'Children mock' ) ).toBeInTheDocument();
14 | } );
15 |
16 | it( 'should accept a Description component', () => {
17 | render(
18 | 'Description mock' }>
19 | Children mock
20 |
21 | );
22 |
23 | expect( screen.queryByText( 'Children mock' ) ).toBeInTheDocument();
24 | expect( screen.queryByText( 'Description mock' ) ).toBeInTheDocument();
25 | } );
26 | } );
27 |
--------------------------------------------------------------------------------
/client/settings/settings-section/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 |
4 | const SettingsSectionWrapper = styled.div`
5 | display: flex;
6 | flex-flow: column;
7 | margin-bottom: 24px;
8 |
9 | &:last-child {
10 | margin-bottom: 0;
11 | }
12 |
13 | @media ( min-width: 800px ) {
14 | flex-flow: row;
15 | }
16 | `;
17 |
18 | const DescriptionWrapper = styled.div`
19 | flex: 0 1 auto;
20 | margin-bottom: 24px;
21 |
22 | @media ( min-width: 800px ) {
23 | flex: 0 0 25%;
24 | margin: 0 32px 0 0;
25 | }
26 |
27 | h2 {
28 | font-size: 16px;
29 | line-height: 24px;
30 | }
31 |
32 | p {
33 | font-size: 13px;
34 | line-height: 17.89px;
35 | margin: 12px 0;
36 | }
37 |
38 | > :last-child {
39 | margin-bottom: 0;
40 | }
41 | `;
42 |
43 | const Controls = styled.div`
44 | flex: 1 1 auto;
45 | `;
46 |
47 | const SettingsSection = ( {
48 | Description = () => null,
49 | children,
50 | ...restProps
51 | } ) => (
52 |
53 |
54 |
55 |
56 | { children }
57 |
58 | );
59 |
60 | export default SettingsSection;
61 |
--------------------------------------------------------------------------------
/client/settings/stripe-auth-account/stripe-auth-diagram.js:
--------------------------------------------------------------------------------
1 | import StripeMark from 'wcstripe/brand-logos/stripe-mark';
2 | import WooLogo from 'wcstripe/brand-logos/woo-white';
3 |
4 | /**
5 | * StripeAuthDiagram component.
6 | *
7 | * @return {JSX.Element} The rendered StripeAuthDiagram component.
8 | */
9 | const StripeAuthDiagram = () => {
10 | return (
11 |
16 | );
17 | };
18 |
19 | export default StripeAuthDiagram;
20 |
--------------------------------------------------------------------------------
/client/settings/stripe-auth-account/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/abstracts/styles.scss';
2 |
3 | .woocommerce-stripe-auth {
4 | > p {
5 | text-wrap: pretty;
6 | }
7 | &__diagram {
8 | display: flex;
9 | flex-direction: row;
10 | align-items: center;
11 | justify-content: center;
12 | gap: $grid-unit-20;
13 | margin: $grid-unit-40 0;
14 |
15 | &__dotted-line {
16 | position: relative;
17 | border-top: 1px dashed $gray-600;
18 | margin: 1px;
19 | width: 50px;
20 |
21 | &::before,
22 | &::after {
23 | content: '';
24 | position: absolute;
25 | top: -2.5px;
26 | width: 4px;
27 | height: 4px;
28 | background-color: $gray-600;
29 | border-radius: 50%;
30 | }
31 | &::before {
32 | left: -4px;
33 | }
34 | &::after {
35 | right: -4px;
36 | }
37 | }
38 | img {
39 | border-radius: 6px;
40 | }
41 | .woocommerce-stripe-woo-white-logo {
42 | background-color: $studio-woocommerce-purple;
43 | padding: 5px;
44 | }
45 | }
46 | &__actions {
47 | display: flex;
48 | flex-direction: row;
49 | align-items: center;
50 | gap: $grid-unit-20;
51 | }
52 | &__status-panel {
53 | display: flex;
54 | flex-direction: row;
55 | align-items: center;
56 | gap: $grid-unit-20;
57 |
58 | .wcstripe-status {
59 | display: flex;
60 | flex-direction: row;
61 | align-items: center;
62 | gap: $grid-unit;
63 | }
64 | .wcstripe-status-text {
65 | font-size: 11px;
66 | font-weight: 500;
67 | text-transform: uppercase;
68 | }
69 | }
70 | &__help {
71 | color: $gray-700;
72 | font-size: $helptext-font-size;
73 | font-style: normal;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/client/settings/styles.scss:
--------------------------------------------------------------------------------
1 | #wc-stripe-account-settings-container {
2 | box-sizing: border-box;
3 | }
4 |
5 | #wc-stripe-new-account-container {
6 | display: flex;
7 | justify-content: center;
8 | }
9 |
--------------------------------------------------------------------------------
/client/settings/upe-toggle/context.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | const UpeToggleContext = createContext( {
4 | isUpeEnabled: false,
5 | setIsUpeEnabled: () => null,
6 | status: 'resolved',
7 | } );
8 |
9 | export default UpeToggleContext;
10 |
--------------------------------------------------------------------------------
/client/stripe-utils/index.js:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 |
--------------------------------------------------------------------------------
/client/styles/abstracts/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Rem output with px fallback
2 | @mixin font-size( $sizeValue: 16, $lineHeight: false ) {
3 | font-size: $sizeValue + px;
4 | font-size: ( $sizeValue / 16 ) + rem;
5 | @if ( $lineHeight ) {
6 | line-height: $lineHeight;
7 | }
8 | }
9 |
10 | // Adds animation to placeholder section
11 | @mixin placeholder( $bg-color: $core-light-gray-500 ) {
12 | animation: loading-fade 1.6s ease-in-out infinite;
13 | background: $bg-color;
14 | color: transparent;
15 |
16 | &::after {
17 | content: '\00a0';
18 | }
19 | }
20 |
21 | @mixin modal-footer-buttons {
22 | display: flex;
23 | justify-content: flex-end;
24 |
25 | > * {
26 | &:not( :first-child ) {
27 | margin-left: $grid-unit-20;
28 | }
29 | }
30 | }
31 |
32 | @mixin modal-footer-buttons-left {
33 | display: flex;
34 |
35 | > * {
36 | &:not( :last-child ) {
37 | margin-right: $grid-unit-20;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/styles/abstracts/_variables.scss:
--------------------------------------------------------------------------------
1 | // Gaps (from woocommerce-admin).
2 | $gap-largest: 40px;
3 | $gap-larger: 36px;
4 | $gap-large: 24px;
5 | $gap: 16px;
6 | $gap-small: 12px;
7 | $gap-smaller: 8px;
8 | $gap-smallest: 4px;
9 |
10 | // Modals
11 | $modal-max-width: 500px;
12 |
--------------------------------------------------------------------------------
/client/styles/abstracts/styles.scss:
--------------------------------------------------------------------------------
1 | @import 'node_modules/@wordpress/base-styles/_colors.scss';
2 | @import 'node_modules/@wordpress/base-styles/_colors.native.scss';
3 | @import 'node_modules/@wordpress/base-styles/_variables.scss';
4 | @import 'node_modules/@wordpress/base-styles/_mixins.scss';
5 | @import 'node_modules/@wordpress/base-styles/_breakpoints.scss';
6 | @import 'node_modules/@wordpress/base-styles/_animations.scss';
7 | @import 'node_modules/@wordpress/base-styles/_z-index.scss';
8 | @import './_colors';
9 | @import './_breakpoints';
10 | @import './_mixins';
11 | @import './_variables';
12 |
--------------------------------------------------------------------------------
/client/tracking/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import { recordEvent } from '..';
2 |
3 | jest.mock( '@wordpress/dom-ready', () => ( cb ) => cb() );
4 |
5 | describe( 'tracking', () => {
6 | beforeEach( () => {
7 | global.wcTracks = undefined;
8 |
9 | global.wc_stripe_settings_params = {
10 | is_test_mode: 'yes',
11 | plugin_version: '1.2.3',
12 | };
13 | } );
14 |
15 | it( 'does not fail if the global library is not present in the DOM', () => {
16 | expect( () =>
17 | recordEvent( 'event_name', { value: '1' } )
18 | ).not.toThrow();
19 | } );
20 |
21 | it( 'does not track if tracking is not enabled', () => {
22 | const recordEventMock = jest.fn();
23 | global.wcTracks = { recordEvent: recordEventMock, isEnabled: false };
24 |
25 | recordEvent( 'event_name', { value: '1' } );
26 |
27 | expect( recordEventMock ).not.toHaveBeenCalled();
28 | } );
29 | it( 'does tracks the event with its payload', () => {
30 | const recordEventMock = jest.fn();
31 | global.wcTracks = { recordEvent: recordEventMock, isEnabled: true };
32 |
33 | recordEvent( 'event_name', { value: '1' } );
34 |
35 | expect( recordEventMock ).toHaveBeenCalledWith( 'event_name', {
36 | value: '1',
37 | is_test_mode: 'yes',
38 | stripe_version: '1.2.3',
39 | } );
40 | } );
41 | } );
42 |
--------------------------------------------------------------------------------
/client/utils/index.js:
--------------------------------------------------------------------------------
1 | /* global wc_add_to_cart_variation_params */
2 | export const getAddToCartVariationParams = ( key ) => {
3 | // eslint-disable-next-line camelcase
4 | const wcAddToCartVariationParams = wc_add_to_cart_variation_params;
5 | if ( ! wcAddToCartVariationParams || ! wcAddToCartVariationParams[ key ] ) {
6 | return null;
7 | }
8 |
9 | return wcAddToCartVariationParams[ key ];
10 | };
11 |
--------------------------------------------------------------------------------
/client/utils/use-confirm-navigation.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useCallback, useRef } from '@wordpress/element';
2 |
3 | /**
4 | * Hook for displaying a confirmation message before navigate.
5 | *
6 | * Usage:
7 | * - const callback = useConfirmNavigation( true );
8 | * useEffect( callback , [ callback, otherDependency ] );
9 | *
10 | * @param {boolean} displayPrompt Whether we should prompt the message or not
11 | * @return {Function} The callback to execute
12 | */
13 | const useConfirmNavigation = ( displayPrompt ) => {
14 | const savedDisplayPrompt = useRef();
15 |
16 | useEffect( () => {
17 | savedDisplayPrompt.current = displayPrompt;
18 | } );
19 |
20 | return useCallback( () => {
21 | const doDisplayPrompt = savedDisplayPrompt.current;
22 |
23 | if ( ! doDisplayPrompt ) {
24 | return;
25 | }
26 |
27 | const handler = ( event ) => {
28 | event.preventDefault();
29 | event.returnValue = '';
30 | };
31 |
32 | // eslint-disable-next-line @wordpress/no-global-event-listener
33 | window.addEventListener( 'beforeunload', handler );
34 |
35 | return () => {
36 | // eslint-disable-next-line @wordpress/no-global-event-listener
37 | window.removeEventListener( 'beforeunload', handler );
38 | };
39 | }, [] );
40 | };
41 |
42 | export default useConfirmNavigation;
43 |
--------------------------------------------------------------------------------
/includes/admin/class-wc-stripe-rest-base-controller.php:
--------------------------------------------------------------------------------
1 | ' );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/includes/class-wc-stripe-exception.php:
--------------------------------------------------------------------------------
1 | localized_message = $localized_message;
31 | parent::__construct( $error_message );
32 | }
33 |
34 | /**
35 | * Returns the localized message.
36 | *
37 | * @since 4.0.2
38 | * @return string
39 | */
40 | public function getLocalizedMessage() {
41 | return $this->localized_message;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/includes/class-wc-stripe-mode.php:
--------------------------------------------------------------------------------
1 | =' );
13 | }
14 |
15 | public static function is_wc_supported() {
16 | return version_compare( WC_VERSION, self::MIN_WC_VERSION, '>=' );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/includes/compat/class-wc-stripe-woo-compat-utils.php:
--------------------------------------------------------------------------------
1 | connect = $connect;
38 | }
39 |
40 | /**
41 | * Initiate OAuth flow.
42 | *
43 | * @param array $request POST request.
44 | *
45 | * @return array|WP_Error
46 | */
47 | public function post( $request ) {
48 |
49 | $data = $request->get_json_params();
50 | $response = $this->connect->get_oauth_url( isset( $data['returnUrl'] ) ? $data['returnUrl'] : '' );
51 |
52 | if ( is_wp_error( $response ) ) {
53 |
54 | WC_Stripe_Logger::log( $response, __CLASS__ );
55 |
56 | return new WP_Error(
57 | $response->get_error_code(),
58 | $response->get_error_message(),
59 | [ 'status' => 400 ]
60 | );
61 | }
62 |
63 | return [
64 | 'success' => true,
65 | 'oauthUrl' => $response,
66 | ];
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/includes/deprecated/class-wc-stripe-apple-pay.php:
--------------------------------------------------------------------------------
1 | stripe_id = self::STRIPE_ID;
21 | $this->title = __( 'EPS', 'woocommerce-gateway-stripe' );
22 | $this->is_reusable = false;
23 | $this->supported_currencies = [ WC_Stripe_Currency_Code::EURO ];
24 | $this->label = __( 'EPS', 'woocommerce-gateway-stripe' );
25 | $this->description = __(
26 | 'EPS is an Austria-based payment method that allows customers to complete transactions online using their bank credentials.',
27 | 'woocommerce-gateway-stripe'
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/includes/payment-methods/class-wc-stripe-upe-payment-method-giropay.php:
--------------------------------------------------------------------------------
1 | stripe_id = self::STRIPE_ID;
21 | $this->title = __( 'giropay', 'woocommerce-gateway-stripe' );
22 | $this->is_reusable = false;
23 | $this->supported_currencies = [ WC_Stripe_Currency_Code::EURO ];
24 | $this->label = __( 'giropay', 'woocommerce-gateway-stripe' );
25 | $this->description = __(
26 | 'Expand your business with giropay — Germany’s second most popular payment system.',
27 | 'woocommerce-gateway-stripe'
28 | );
29 | }
30 |
31 | /**
32 | * Returns boolean dependent on whether payment method
33 | * can be used at checkout
34 | *
35 | * @param int|null $order_id
36 | * @param string|null $account_domestic_currency The account's default currency.
37 | * @return bool
38 | */
39 | public function is_enabled_at_checkout( $order_id = null, $account_domestic_currency = null ) {
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/includes/payment-methods/class-wc-stripe-upe-payment-method-p24.php:
--------------------------------------------------------------------------------
1 | stripe_id = self::STRIPE_ID;
21 | $this->title = __( 'Przelewy24', 'woocommerce-gateway-stripe' );
22 | $this->is_reusable = false;
23 | $this->supported_currencies = [ WC_Stripe_Currency_Code::EURO, WC_Stripe_Currency_Code::POLISH_ZLOTY ];
24 | $this->label = __( 'Przelewy24', 'woocommerce-gateway-stripe' );
25 | $this->description = __(
26 | 'Przelewy24 is a Poland-based payment method aggregator that allows customers to complete transactions online using bank transfers and other methods.',
27 | 'woocommerce-gateway-stripe'
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/includes/payment-methods/class-wc-stripe-upe-payment-method-sofort.php:
--------------------------------------------------------------------------------
1 | stripe_id = self::STRIPE_ID;
21 | $this->title = __( 'Sofort', 'woocommerce-gateway-stripe' );
22 | $this->is_reusable = true;
23 | $this->supported_currencies = [ WC_Stripe_Currency_Code::EURO ];
24 | $this->label = __( 'Sofort', 'woocommerce-gateway-stripe' );
25 | $this->supports[] = 'subscriptions';
26 | $this->supports[] = 'tokenization';
27 | $this->supports[] = 'multiple_subscriptions';
28 | $this->description = __(
29 | 'Accept secure bank transfers from Austria, Belgium, Germany, Italy, Netherlands, and Spain.',
30 | 'woocommerce-gateway-stripe'
31 | );
32 |
33 | // Add support for pre-orders.
34 | $this->maybe_init_pre_orders();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/includes/payment-tokens/class-wc-stripe-cc-payment-token.php:
--------------------------------------------------------------------------------
1 | extra_data['fingerprint'] = '';
28 |
29 | parent::__construct( $token );
30 | }
31 |
32 | /**
33 | * Checks if the payment method token is equal a provided payment method.
34 | *
35 | * @inheritDoc
36 | */
37 | public function is_equal_payment_method( $payment_method ): bool {
38 | if ( WC_Stripe_Payment_Methods::CARD === $payment_method->type
39 | && ( $payment_method->card->fingerprint ?? null ) === $this->get_fingerprint() ) {
40 | return true;
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/includes/payment-tokens/interface-wc-stripe-payment-method-comparison.php:
--------------------------------------------------------------------------------
1 | get_prop( 'fingerprint', $context );
19 | }
20 |
21 | /**
22 | * Set the token fingerprint (unique identifier).
23 | *
24 | * @since 9.0.0
25 | * @param string $fingerprint The fingerprint.
26 | */
27 | public function set_fingerprint( string $fingerprint ) {
28 | $this->set_prop( 'fingerprint', $fingerprint );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.{js,jsx,ts,tsx}': [ 'npm run format:js', 'eslint' ],
3 | '*.{scss,css}': [ 'npm run lint:css' ],
4 | '*.php':
5 | './vendor/bin/phpcs --standard=phpcs.xml.dist -n --basepath=. --colors',
6 | 'composer.json': 'composer validate --strict --no-check-all',
7 | };
8 |
--------------------------------------------------------------------------------
/templates/emails/failed-renewal-authentication.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | ' . esc_html__( 'Authorize the payment »', 'woocommerce-gateway-stripe' ) . '' ), [ 'a' => [ 'href' => true ] ] );
13 | ?>
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/templates/emails/plain/failed-preorder-authentication.php:
--------------------------------------------------------------------------------
1 | get_custom_message() ) :
15 |
16 | echo "----------\n\n";
17 | echo esc_html( wptexturize( $email->get_custom_message() ) ) . "\n\n";
18 | echo "----------\n\n";
19 |
20 | endif;
21 |
22 |
23 | echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
24 |
25 | do_action( 'woocommerce_subscriptions_email_order_details', $order, $sent_to_admin, $plain_text, $email );
26 |
27 | echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
28 |
29 | echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) );
30 |
--------------------------------------------------------------------------------
/templates/emails/plain/failed-renewal-authentication.php:
--------------------------------------------------------------------------------
1 |