├── .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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/afterpay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/alipay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/bancontact.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/images/bank-debit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/boleto.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/cards.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/cashapp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/images/jcb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/klarna.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/images/mastercard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/stripe.svg: -------------------------------------------------------------------------------- 1 | Asset 32 2 | -------------------------------------------------------------------------------- /assets/images/visa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/wechat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 3 | 10 | 11 | 14 | 16 | 18 | 19 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/payment-method-icons/link/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | Asset 32 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 | 3 | 5 | -------------------------------------------------------------------------------- /client/settings/payment-settings/promotional-banner/illustrations/reconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | 11 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /client/settings/payments-and-transactions-section/statement-preview/icons/creditCard.js: -------------------------------------------------------------------------------- 1 | export const CreditCardIcon = ( 2 | 11 | 20 | 26 | 27 | 28 | 29 | 30 | 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 |
36 |

{ title }

37 |
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 |
12 | 13 |
14 | 15 |
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 |