├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── e2e_tests.yml │ ├── js_tests.yml │ ├── php_tests.yml │ ├── run-qit.yml │ └── update-requires-headers.yml ├── .gitignore ├── .gitmodules ├── .husky ├── post-checkout ├── post-merge ├── post-rewrite ├── pre-commit └── pre-push ├── .nvmrc ├── .phpcs.security.xml ├── CONTRIBUTING.md ├── CREDITS.md ├── LICENSE.md ├── README.md ├── assets └── stylesheets │ ├── _colors.scss │ ├── _components.scss │ ├── _fix-image-paths.scss │ ├── banner.scss │ ├── migration_to_wcshipping_admin_notice.scss │ └── style.scss ├── babel.config.js ├── bin ├── composer-installer.sh ├── docker-setup.sh ├── get-latest-version.php ├── jurassic-tube-setup.sh └── wc-phpcbf.sh ├── changelog.txt ├── classes ├── class-wc-connect-account-settings.php ├── class-wc-connect-api-client-live.php ├── class-wc-connect-api-client.php ├── class-wc-connect-cart-validation.php ├── class-wc-connect-compatibility-wc30.php ├── class-wc-connect-compatibility-wc69.php ├── class-wc-connect-compatibility-wcshipping-packages.php ├── class-wc-connect-compatibility.php ├── class-wc-connect-continents.php ├── class-wc-connect-custom-surcharge.php ├── class-wc-connect-debug-tools.php ├── class-wc-connect-error-notice.php ├── class-wc-connect-extension-compatibility.php ├── class-wc-connect-functions.php ├── class-wc-connect-help-view.php ├── class-wc-connect-jetpack.php ├── class-wc-connect-label-reports.php ├── class-wc-connect-logger.php ├── class-wc-connect-note-dhl-live-rates-available.php ├── class-wc-connect-nux.php ├── class-wc-connect-options.php ├── class-wc-connect-order-presenter.php ├── class-wc-connect-package-settings.php ├── class-wc-connect-payment-gateway.php ├── class-wc-connect-payment-methods-store.php ├── class-wc-connect-paypal-ec.php ├── class-wc-connect-privacy.php ├── class-wc-connect-service-schemas-store.php ├── class-wc-connect-service-schemas-validator.php ├── class-wc-connect-service-settings-store.php ├── class-wc-connect-settings-pages.php ├── class-wc-connect-shipping-label.php ├── class-wc-connect-shipping-method.php ├── class-wc-connect-taxjar-integration.php ├── class-wc-connect-tracks.php ├── class-wc-connect-utils.php ├── class-wc-connect-wcst-to-wcshipping-migration-state-enum.php ├── class-wc-rest-connect-account-settings-controller.php ├── class-wc-rest-connect-address-normalization-controller.php ├── class-wc-rest-connect-assets-controller.php ├── class-wc-rest-connect-base-controller.php ├── class-wc-rest-connect-migration-flag-controller.php ├── class-wc-rest-connect-packages-controller.php ├── class-wc-rest-connect-self-help-controller.php ├── class-wc-rest-connect-service-data-refresh-controller.php ├── class-wc-rest-connect-services-controller.php ├── class-wc-rest-connect-shipping-carrier-controller.php ├── class-wc-rest-connect-shipping-carrier-delete-controller.php ├── class-wc-rest-connect-shipping-carrier-types-controller.php ├── class-wc-rest-connect-shipping-carriers-controller.php ├── class-wc-rest-connect-shipping-label-controller.php ├── class-wc-rest-connect-shipping-label-eligibility-controller.php ├── class-wc-rest-connect-shipping-label-preview-controller.php ├── class-wc-rest-connect-shipping-label-print-controller.php ├── class-wc-rest-connect-shipping-label-refund-controller.php ├── class-wc-rest-connect-shipping-label-status-controller.php ├── class-wc-rest-connect-shipping-rates-controller.php ├── class-wc-rest-connect-subscription-activate-controller.php ├── class-wc-rest-connect-subscriptions-controller.php ├── class-wc-rest-connect-tos-controller.php ├── class-wc-rest-connect-wcshipping-compatibility-packages-controller.php └── wc-api-dev │ ├── class-wc-rest-dev-data-continents-controller.php │ └── class-wc-rest-dev-data-controller.php ├── client ├── admin-pointers.js ├── api │ ├── index.js │ ├── request.js │ └── url.js ├── apps │ ├── plugin-status │ │ ├── health.js │ │ ├── index.js │ │ ├── indicator.js │ │ ├── log.js │ │ ├── services.js │ │ ├── state │ │ │ ├── action-types.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── style.scss │ │ ├── test │ │ │ └── health.test.js │ │ ├── view.js │ │ └── woocommerce-services-indicator.js │ ├── print-test-label │ │ ├── index.js │ │ ├── state │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── style.scss │ │ └── view.js │ ├── settings │ │ ├── index.js │ │ ├── redux-middleware.js │ │ ├── style.scss │ │ └── view-wrapper.js │ ├── shipping-label │ │ ├── index.js │ │ ├── redux-middleware.js │ │ ├── style.scss │ │ ├── test │ │ │ └── view-wrapper-label.js │ │ ├── view-wrapper-label.js │ │ └── view-wrapper-tracking.js │ └── shipping-settings │ │ ├── index.js │ │ └── view-wrapper.js ├── banner.js ├── calypso-stubs │ ├── components │ │ └── data │ │ │ └── query-stored-cards │ │ │ └── index.js │ ├── config.js │ ├── extensions │ │ └── woocommerce │ │ │ ├── lib │ │ │ └── nav-utils.js │ │ │ ├── state │ │ │ ├── selectors │ │ │ │ └── plugins.js │ │ │ └── sites │ │ │ │ ├── http-request.js │ │ │ │ └── request.js │ │ │ └── woocommerce-services │ │ │ ├── lib │ │ │ └── utils │ │ │ │ ├── get-packaging-manager-link.js │ │ │ │ └── get-product-link.js │ │ │ └── views │ │ │ └── label-settings │ │ │ └── add-credit-card-modal.js │ └── state │ │ ├── data-layer │ │ ├── wpcom-api-middleware.js │ │ └── wpcom-http │ │ │ └── index.js │ │ ├── selectors │ │ └── is-rtl.js │ │ ├── sites │ │ └── selectors.js │ │ └── stored-cards │ │ └── selectors.js ├── components │ ├── error-notice │ │ └── index.js │ ├── foldable-card │ │ ├── index.js │ │ └── style.scss │ ├── forms │ │ ├── form-button │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── form-fieldset │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── form-label │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── form-legend │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── form-radio │ │ │ └── index.js │ │ ├── form-section-heading │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── form-select │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── form-setting-explanation │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ └── text-control-with-affixes │ │ │ ├── index.js │ │ │ └── style.scss │ ├── migration │ │ ├── constants.js │ │ ├── feature-announcement.jsx │ │ ├── icons.jsx │ │ ├── images │ │ │ └── wcshipping-migration.jpg │ │ ├── migration-runner.js │ │ └── style.scss │ ├── notice │ │ ├── README.md │ │ ├── docs │ │ │ └── example.jsx │ │ ├── index.jsx │ │ ├── notice-action.jsx │ │ ├── style.scss │ │ └── test │ │ │ └── index.js │ ├── payment-logo │ │ ├── README.md │ │ ├── docs │ │ │ └── example.jsx │ │ ├── images │ │ │ ├── cc-amex.svg │ │ │ ├── cc-diners.svg │ │ │ ├── cc-discover.svg │ │ │ ├── cc-jcb.svg │ │ │ ├── cc-mastercard.svg │ │ │ ├── cc-placeholder.svg │ │ │ ├── cc-unionpay.svg │ │ │ └── cc-visa.svg │ │ ├── index.jsx │ │ └── style.scss │ ├── segmented-control │ │ ├── README.md │ │ ├── docs │ │ │ └── example.jsx │ │ ├── index.jsx │ │ ├── item.jsx │ │ ├── simplified.jsx │ │ └── style.scss │ ├── spinner │ │ ├── README.md │ │ ├── docs │ │ │ └── example.jsx │ │ ├── index.jsx │ │ └── style.scss │ └── toggle │ │ ├── index.js │ │ └── style.scss ├── extensions │ └── woocommerce │ │ ├── README.md │ │ ├── app │ │ ├── dashboard │ │ │ ├── setup │ │ │ │ └── style.scss │ │ │ ├── style.scss │ │ │ └── widgets │ │ │ │ ├── inventory-widget │ │ │ │ └── style.scss │ │ │ │ └── stats-widget │ │ │ │ └── style.scss │ │ ├── order │ │ │ ├── order-activity-log │ │ │ │ ├── day.js │ │ │ │ ├── event.js │ │ │ │ ├── events.js │ │ │ │ └── style.scss │ │ │ ├── order-created │ │ │ │ └── style.scss │ │ │ ├── order-customer │ │ │ │ └── style.scss │ │ │ ├── order-details │ │ │ │ └── style.scss │ │ │ ├── order-fulfillment │ │ │ │ └── style.scss │ │ │ ├── order-payment │ │ │ │ └── style.scss │ │ │ └── style.scss │ │ ├── orders │ │ │ └── style.scss │ │ ├── product-categories │ │ │ └── style.scss │ │ ├── products │ │ │ ├── product-form.scss │ │ │ └── products-list.scss │ │ ├── promotions │ │ │ ├── README.md │ │ │ ├── fields │ │ │ │ ├── README.md │ │ │ │ └── style.scss │ │ │ ├── promotion-form.scss │ │ │ ├── promotions-list.scss │ │ │ └── style.scss │ │ ├── reviews │ │ │ └── style.scss │ │ ├── settings │ │ │ ├── email │ │ │ │ ├── email-settings │ │ │ │ │ └── style.scss │ │ │ │ └── mailchimp │ │ │ │ │ └── style.scss │ │ │ ├── payments │ │ │ │ └── style.scss │ │ │ ├── shipping │ │ │ │ ├── shipping-zone │ │ │ │ │ └── style.scss │ │ │ │ └── style.scss │ │ │ └── taxes │ │ │ │ └── style.scss │ │ └── store-stats │ │ │ ├── referrers │ │ │ ├── chart │ │ │ │ └── style.scss │ │ │ └── style.scss │ │ │ ├── store-stats-chart │ │ │ └── style.scss │ │ │ ├── store-stats-module │ │ │ └── style.scss │ │ │ ├── store-stats-orders-chart │ │ │ └── style.scss │ │ │ ├── store-stats-referrer-conv-widget │ │ │ └── style.scss │ │ │ ├── store-stats-referrer-widget-base │ │ │ └── style.scss │ │ │ ├── store-stats-referrer-widget │ │ │ └── style.scss │ │ │ ├── store-stats-widget-list │ │ │ └── style.scss │ │ │ └── style.scss │ │ ├── components │ │ ├── action-header │ │ │ └── style.scss │ │ ├── address-view │ │ │ └── style.scss │ │ ├── bulk-select │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── button │ │ │ └── style.scss │ │ ├── card │ │ │ └── style.scss │ │ ├── compact-tinymce │ │ │ └── style.scss │ │ ├── d3 │ │ │ ├── base │ │ │ │ ├── README.md │ │ │ │ └── style.scss │ │ │ └── horizontal-bar │ │ │ │ └── style.scss │ │ ├── dashboard-widget │ │ │ ├── README.md │ │ │ └── style.scss │ │ ├── delta │ │ │ └── style.scss │ │ ├── extended-header │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── filtered-list │ │ │ └── style.scss │ │ ├── form-click-to-edit-input │ │ │ └── style.scss │ │ ├── form-dimensions-input │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── form-location-select │ │ │ └── README.md │ │ ├── image-thumb │ │ │ └── style.scss │ │ ├── list │ │ │ ├── list-header │ │ │ │ └── style.scss │ │ │ ├── list-item-field │ │ │ │ └── style.scss │ │ │ ├── list-item │ │ │ │ └── style.scss │ │ │ ├── list │ │ │ │ └── style.scss │ │ │ └── style.scss │ │ ├── location-flag │ │ │ └── style.scss │ │ ├── order-status │ │ │ ├── README.md │ │ │ └── style.scss │ │ ├── price-input │ │ │ ├── README.md │ │ │ └── style.scss │ │ ├── product-image-uploader │ │ │ └── style.scss │ │ ├── product-reviews-widget │ │ │ └── style.scss │ │ ├── query-locations │ │ │ └── index.js │ │ ├── reading-widget │ │ │ └── style.scss │ │ ├── share-widget │ │ │ └── style.scss │ │ ├── store-address │ │ │ └── style.scss │ │ ├── table │ │ │ ├── README.md │ │ │ └── style.scss │ │ └── woocommerce-colophon │ │ │ └── style.scss │ │ ├── lib │ │ ├── analytics │ │ │ └── README.md │ │ ├── currency │ │ │ └── index.js │ │ ├── formatted-variation-name │ │ │ └── README.md │ │ ├── generate-variations │ │ │ └── README.md │ │ ├── get-keyboard-handler │ │ │ └── README.md │ │ ├── get-payment-method-details │ │ │ ├── README.md │ │ │ └── details-mappings.json │ │ ├── get-required-plugins.js │ │ ├── nav-utils.js │ │ ├── order-status │ │ │ └── index.js │ │ └── order-values │ │ │ ├── index.js │ │ │ └── totals.js │ │ ├── package.json │ │ ├── state │ │ ├── README.md │ │ ├── action-list │ │ │ ├── README.md │ │ │ └── actions.js │ │ ├── action-types.js │ │ ├── constants.js │ │ ├── data-layer │ │ │ ├── action-list │ │ │ │ └── index.js │ │ │ ├── data │ │ │ │ └── locations │ │ │ │ │ └── index.js │ │ │ ├── orders │ │ │ │ ├── index.js │ │ │ │ └── notes │ │ │ │ │ └── index.js │ │ │ ├── shipping-classes │ │ │ │ └── index.js │ │ │ ├── ui │ │ │ │ └── woocommerce-services │ │ │ │ │ └── index.js │ │ │ └── utils.js │ │ ├── helpers.js │ │ ├── selectors │ │ │ └── plugins.js │ │ ├── sites │ │ │ ├── README.md │ │ │ ├── customers │ │ │ │ ├── README.md │ │ │ │ └── test │ │ │ │ │ └── fixtures │ │ │ │ │ └── customers.json │ │ │ ├── data │ │ │ │ ├── counts │ │ │ │ │ ├── actions.js │ │ │ │ │ └── test │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ └── count.json │ │ │ │ ├── currencies │ │ │ │ │ └── README.md │ │ │ │ └── locations │ │ │ │ │ ├── README.md │ │ │ │ │ ├── actions.js │ │ │ │ │ ├── reducer.js │ │ │ │ │ └── selectors.js │ │ │ ├── meta │ │ │ │ └── taxrates │ │ │ │ │ └── README.md │ │ │ ├── orders │ │ │ │ ├── README.md │ │ │ │ ├── actions.js │ │ │ │ ├── activity-log │ │ │ │ │ ├── README.md │ │ │ │ │ └── selectors.js │ │ │ │ ├── notes │ │ │ │ │ ├── README.md │ │ │ │ │ ├── actions.js │ │ │ │ │ ├── reducer.js │ │ │ │ │ └── selectors.js │ │ │ │ ├── reducer.js │ │ │ │ ├── refunds │ │ │ │ │ ├── README.md │ │ │ │ │ ├── reducer.js │ │ │ │ │ └── selectors.js │ │ │ │ ├── selectors.js │ │ │ │ ├── send-invoice │ │ │ │ │ ├── README.md │ │ │ │ │ └── reducer.js │ │ │ │ └── utils.js │ │ │ ├── payment-methods │ │ │ │ └── README.md │ │ │ ├── product-categories │ │ │ │ └── README.md │ │ │ ├── products │ │ │ │ └── README.md │ │ │ ├── promotions │ │ │ │ └── README.md │ │ │ ├── request.js │ │ │ ├── review-replies │ │ │ │ └── README.md │ │ │ ├── reviews │ │ │ │ └── README.md │ │ │ ├── selectors.js │ │ │ ├── settings │ │ │ │ ├── README.md │ │ │ │ ├── email │ │ │ │ │ └── README.md │ │ │ │ ├── general │ │ │ │ │ └── README.md │ │ │ │ ├── mailchimp │ │ │ │ │ ├── queryLists.jsx │ │ │ │ │ ├── querySettings.jsx │ │ │ │ │ └── querySyncStatus.jsx │ │ │ │ └── products │ │ │ │ │ ├── README.md │ │ │ │ │ ├── actions.js │ │ │ │ │ └── selectors.js │ │ │ ├── shipping-classes │ │ │ │ ├── actions.js │ │ │ │ ├── reducers.js │ │ │ │ └── selectors.js │ │ │ ├── shipping-methods │ │ │ │ └── selectors.js │ │ │ ├── shipping-zone-locations │ │ │ │ ├── README.md │ │ │ │ ├── helpers.js │ │ │ │ └── selectors.js │ │ │ ├── shipping-zone-methods │ │ │ │ ├── actions.js │ │ │ │ ├── reducer.js │ │ │ │ └── selectors.js │ │ │ ├── shipping-zones │ │ │ │ ├── README.md │ │ │ │ ├── reducer.js │ │ │ │ └── selectors.js │ │ │ └── status │ │ │ │ └── wc-api │ │ │ │ └── actions.js │ │ └── ui │ │ │ ├── README.md │ │ │ ├── helpers.js │ │ │ ├── orders │ │ │ ├── README.md │ │ │ └── selectors.js │ │ │ ├── payments │ │ │ ├── README.md │ │ │ ├── currency │ │ │ │ └── README.md │ │ │ └── methods │ │ │ │ └── README.md │ │ │ ├── products │ │ │ └── README.md │ │ │ ├── review-replies │ │ │ └── README.md │ │ │ ├── reviews │ │ │ └── README.md │ │ │ └── shipping │ │ │ └── zones │ │ │ ├── README.md │ │ │ ├── locations │ │ │ ├── README.md │ │ │ ├── helpers.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ │ ├── methods │ │ │ ├── README.md │ │ │ ├── actions.js │ │ │ ├── flat-rate │ │ │ │ ├── README.md │ │ │ │ └── reducer.js │ │ │ ├── free-shipping │ │ │ │ ├── README.md │ │ │ │ └── reducer.js │ │ │ ├── helpers.js │ │ │ ├── local-pickup │ │ │ │ ├── README.md │ │ │ │ └── reducer.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── style.scss │ │ └── woocommerce-services │ │ ├── api │ │ ├── index.js │ │ └── url.js │ │ ├── components │ │ ├── carrier-icon │ │ │ ├── index.js │ │ │ ├── logos │ │ │ │ ├── dhlExpress.png │ │ │ │ ├── ups.png │ │ │ │ └── usps.png │ │ │ └── style.scss │ │ ├── checkbox │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── dropdown │ │ │ └── index.js │ │ ├── field-description │ │ │ └── index.js │ │ ├── field-error │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── info-tooltip │ │ │ └── style.scss │ │ ├── number-field │ │ │ ├── index.js │ │ │ └── number-input.js │ │ ├── price-field │ │ │ └── index.js │ │ ├── query-label-settings │ │ │ └── index.js │ │ ├── query-labels │ │ │ └── index.js │ │ ├── query-packages │ │ │ └── index.js │ │ ├── radio-buttons │ │ │ └── index.js │ │ ├── settings-group-card │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── shipping-classes-field │ │ │ └── index.js │ │ ├── text-field │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── text │ │ │ ├── index.js │ │ │ └── style.scss │ │ └── weight-field │ │ │ └── index.js │ │ ├── lib │ │ ├── get-default-settings-values │ │ │ └── index.js │ │ ├── initialize-labels-state │ │ │ └── index.js │ │ ├── pdf-label-utils │ │ │ └── index.js │ │ ├── special-rates │ │ │ └── index.js │ │ └── utils │ │ │ ├── coerce-values.js │ │ │ ├── get-address-values.js │ │ │ ├── get-box-dimensions.js │ │ │ ├── parse-number.js │ │ │ ├── pdf-support.js │ │ │ ├── print-document.js │ │ │ ├── sanitize-html.js │ │ │ ├── test │ │ │ ├── coerce-values.js │ │ │ └── sanitize-html.js │ │ │ └── tree.js │ │ ├── state │ │ ├── action-types.js │ │ ├── actions.js │ │ ├── label-settings │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── packages │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ ├── selectors.js │ │ │ └── test │ │ │ │ ├── actions.js │ │ │ │ ├── data │ │ │ │ └── initial-state.js │ │ │ │ ├── reducer.js │ │ │ │ └── selectors.js │ │ ├── service-settings │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors │ │ │ │ └── errors.js │ │ ├── shipping-label │ │ │ ├── actions.js │ │ │ ├── constants.js │ │ │ ├── get-rates.js │ │ │ ├── normalize-address.js │ │ │ ├── reducer.js │ │ │ ├── selectors.js │ │ │ └── test │ │ │ │ ├── actions.js │ │ │ │ ├── reducer.js │ │ │ │ └── selectors.js │ │ ├── shipping-method-schemas │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ └── shipping-zone-method-settings │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ ├── style.scss │ │ └── views │ │ ├── carrier-accounts │ │ ├── dialog-styles.scss │ │ ├── dynamic-settings-form.js │ │ ├── dynamic-settings.js │ │ ├── index.js │ │ ├── list-item.js │ │ ├── style.scss │ │ ├── test │ │ │ ├── dynamic-settings-form.test.js │ │ │ ├── dynamic-settings.js │ │ │ ├── index.js │ │ │ └── ups-settings-form.test.js │ │ └── ups-settings-form.js │ │ ├── label-settings │ │ ├── index.js │ │ ├── label-payment-method.js │ │ ├── label-settings.js │ │ └── style.scss │ │ ├── live-rates-carriers-list │ │ ├── carriers-list.js │ │ ├── index.js │ │ ├── style.scss │ │ └── test │ │ │ └── live-rates-carriers-list.test.js │ │ ├── migrator-settings │ │ ├── index.js │ │ └── style.scss │ │ ├── packages │ │ ├── edit-package.js │ │ ├── index.js │ │ ├── input-filters.js │ │ ├── modal-errors.js │ │ ├── package-dialog.js │ │ ├── packages-list-item.js │ │ ├── predefined-packages.js │ │ └── style.scss │ │ ├── service-settings │ │ ├── settings-form │ │ │ ├── index.js │ │ │ ├── settings-group.js │ │ │ ├── settings-item.js │ │ │ └── style.scss │ │ └── shipping-services │ │ │ ├── entry.js │ │ │ ├── group.js │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── shipping-label │ │ ├── label-details-modal.js │ │ ├── label-item-in-progress.js │ │ ├── label-item.js │ │ ├── label-purchase-modal │ │ │ ├── address-step │ │ │ │ ├── fields.js │ │ │ │ ├── index.js │ │ │ │ ├── style.scss │ │ │ │ ├── suggestion.js │ │ │ │ ├── summary.js │ │ │ │ └── unverified.js │ │ │ ├── customs-step │ │ │ │ ├── index.js │ │ │ │ ├── item-row-header.js │ │ │ │ ├── item-row.js │ │ │ │ ├── package-row.js │ │ │ │ └── style.scss │ │ │ ├── index.js │ │ │ ├── packages-step │ │ │ │ ├── add-item.js │ │ │ │ ├── courier-specific │ │ │ │ │ ├── hazmat-types.js │ │ │ │ │ └── usps-hazmat.js │ │ │ │ ├── get-package-descriptions.js │ │ │ │ ├── index.js │ │ │ │ ├── item-info.js │ │ │ │ ├── list.js │ │ │ │ ├── move-item.js │ │ │ │ ├── package-info.js │ │ │ │ ├── package-select.js │ │ │ │ └── style.scss │ │ │ ├── price-summary.js │ │ │ ├── purchase-section │ │ │ │ ├── credit-card-button.js │ │ │ │ ├── index.js │ │ │ │ ├── purchase-button.js │ │ │ │ ├── style.scss │ │ │ │ └── test │ │ │ │ │ └── index.js │ │ │ ├── rates-step │ │ │ │ ├── index.js │ │ │ │ ├── list.js │ │ │ │ ├── shipping-rate.js │ │ │ │ ├── style.scss │ │ │ │ └── test │ │ │ │ │ └── shipping-rate.js │ │ │ ├── shipping-summary.js │ │ │ ├── sidebar.js │ │ │ ├── step-confirmation-button │ │ │ │ ├── index.js │ │ │ │ └── style.scss │ │ │ ├── step-container.js │ │ │ ├── style.scss │ │ │ └── test │ │ │ │ └── sidebar.js │ │ ├── label-refund-modal.js │ │ ├── label-reprint-modal.js │ │ ├── style.scss │ │ ├── test │ │ │ └── label-item.js │ │ ├── tracking-link.js │ │ └── tracking-modal │ │ │ ├── index.js │ │ │ └── style.scss │ │ └── subscriptions-usage │ │ ├── index.js │ │ ├── list-item.js │ │ ├── style.scss │ │ └── test │ │ └── index.js ├── lib │ ├── calypso-boot │ │ └── index.js │ ├── local-api-middleware.js │ ├── pdf-label-utils │ │ └── index.js │ └── utils │ │ ├── local-storage.js │ │ ├── parse-json.js │ │ ├── pdf-support.js │ │ ├── print-document.js │ │ └── sanitize-html.js ├── main.js ├── new-order-taxjar.js ├── provide-public-path.js └── wcshipping-migration-admin-notice.js ├── composer.json ├── composer.lock ├── default.env ├── docker-compose.yml ├── docker ├── mu-dev.php ├── mu-wccon-staging.php ├── php.ini └── wordpress_xdebug │ ├── Dockerfile-php73 │ └── Dockerfile-php81 ├── i18n ├── languages │ └── woocommerce-services.pot └── strings.php ├── images ├── cashier.svg ├── payment-logos │ ├── alipay.svg │ ├── apple-pay.svg │ ├── brazil-tef.svg │ ├── eps.svg │ ├── giropay.svg │ ├── ideal.svg │ ├── p24.svg │ ├── paypal.svg │ ├── sofort.svg │ └── wechat.svg ├── stripe.png ├── wcs-notice.png └── wcshipping-migration.jpg ├── lint-staged.config.js ├── npm-shrinkwrap.json ├── package.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── readme.txt ├── tasks ├── i18n-download.js ├── i18n.js ├── i18njson.js └── release.js ├── tests ├── bin │ ├── e2e-test-integration.js │ ├── get-base-url.js │ ├── install-wc-tests.sh │ ├── install.sh │ ├── wait-for-e2e-build.sh │ └── wc_rest_api_credentials.php ├── client │ ├── jest.config.js │ └── setup-test-framework.js ├── e2e │ ├── README.md │ ├── config │ │ ├── default.json │ │ ├── jest-puppeteer.config.js │ │ ├── jest.config.js │ │ ├── jest.setup.js │ │ ├── jest.test.failure.js │ │ └── travis │ │ │ ├── travis_default-site.conf │ │ │ ├── travis_fastcgi.conf │ │ │ ├── travis_nginx.conf │ │ │ ├── travis_php-fpm.conf │ │ │ └── wc-services-testing-helper.php │ ├── docker │ │ └── initialize.sh │ ├── fixtures │ │ ├── account_settings.js │ │ ├── create-shipping-label.json │ │ ├── index.js │ │ ├── orders.json │ │ ├── products.json │ │ └── test_data.json │ ├── lib │ │ └── reporter │ │ │ └── slack-reporter.js │ ├── mocks │ │ └── index.js │ ├── specs │ │ └── admin │ │ │ ├── 1-activate-extension.test.js │ │ │ ├── 2-shipping-settings.test.js │ │ │ ├── 3-create-shipping-label.test.js │ │ │ └── 4-refund-label.test.js │ └── utils │ │ ├── components.js │ │ ├── flows.js │ │ └── index.js ├── php │ ├── bootstrap.php │ ├── class-wc-connect-api-client-local-test-mock.php │ ├── classes │ │ ├── test-class-wc-connect-compatibility-wcshipping-packages.php │ │ ├── test-class-wc-rest-connect-packages-controller.php │ │ ├── test-class-wc-rest-connect-shipping-carrier-types-controller.php │ │ ├── test-class-wc-rest-connect-shipping-label-controller.php │ │ ├── test-class-wc-rest-connect-subscriptions-controller.php │ │ └── test-class-wc-rest-connect-wcshipping-compatibility-packages-controller.php │ ├── test-class-wc-connect-api-client.php │ ├── test-class-wc-connect-order-presenter.php │ ├── test-class-wc-connect-service-settings-store.php │ ├── test-class-wc-connect-shipping-label.php │ ├── test-class-wc-connect-shipping-method.php │ ├── test-class_wc-connect-nux.php │ ├── test-wc-connect-services-validator.php │ ├── test-woocommerce-connect-client.php │ ├── test-woocommerce-connect-tracks.php │ ├── test-woocommerce-services-compatibility.php │ └── test_data │ │ ├── services_data_mock_with_card.json │ │ └── services_data_mock_with_card_associative.json └── test │ └── helpers │ ├── assets │ ├── README.md │ ├── babel-transform.js │ └── transform.js │ ├── config │ └── index.js │ ├── console │ └── index.js │ ├── use-nock │ ├── README.md │ ├── index.js │ └── integration │ │ └── index.js │ └── use-sinon │ ├── README.md │ └── index.js ├── webpack.config.js ├── woocommerce-services.php └── wordpress_org_assets ├── banner-1544x500.png ├── banner-772x250.png ├── icon-128x128.png ├── icon-160x160.png ├── icon-256x256.png ├── screenshot-1.png └── screenshot-2.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 4 6 | tab_width = 4 7 | indent_style = tab 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # woocommerce app is an artifact of move over from Calypso repo. 2 | # Let's ignore it for now, and delete it later. 3 | client/extensions/woocommerce/* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const calypsoLintConfig = require( './wp-calypso/.eslintrc' ); 2 | const { useE2EEsLintConfig } = require( '@woocommerce/e2e-environment' ); 3 | 4 | calypsoLintConfig.env.jest = true; 5 | Object.assign( calypsoLintConfig.globals, { 6 | page: true, 7 | browser: true, 8 | context: true, 9 | jestPuppeteer: true, 10 | process: true, 11 | console: true, 12 | document: true, 13 | localStorage: true, 14 | window: true, 15 | setTimeout: true, 16 | alert: true, 17 | location: true, 18 | fetch: true, 19 | URL: true, 20 | atob: true, 21 | Blob: true, 22 | Response: true, 23 | global: true, 24 | }); 25 | 26 | module.exports = useE2EEsLintConfig( calypsoLintConfig ); 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: We have moved to Linear! 4 | url: http://linear.app/team/WOOSHIP/new?label=woocommerce-services 5 | about: Please open new issues on Linear. 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Description 7 | 8 | 9 | ### Related issue(s) 10 | 11 | 16 | 17 | ### Steps to reproduce & screenshots/GIFs 18 | 19 | 23 | 24 | ### Checklist 25 | 26 | 27 | - [ ] unit tests 28 | 29 | 30 | - [ ] `changelog.txt` entry added 31 | - [ ] `readme.txt` entry added 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/js_tests.yml: -------------------------------------------------------------------------------- 1 | name: JS Tests 2 | 3 | on: [pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | js_build_and_test: 7 | name: JS Eslint and Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: '10.16.0' 16 | - run: git config --global url.https://github.com/.insteadOf git://github.com/ 17 | - run: npm ci 18 | - run: npm run eslint 19 | - run: npm run test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ 2 | /node_modules/ 3 | project.xml 4 | project.properties 5 | .DS_Store 6 | Thumbs.db 7 | build 8 | .buildpath 9 | .project 10 | .settings* 11 | .vscode 12 | sftp-config.json 13 | /deploy/ 14 | /wc-apidocs/ 15 | i18n/ 16 | release/ 17 | 18 | # Ignore all log files except for .htaccess 19 | /logs/* 20 | !/logs/.htaccess 21 | 22 | /vendor/ 23 | 24 | tests/e2e/config/local-* 25 | .eslintcache 26 | woocommerce-services.zip 27 | dist/ 28 | .cache/ 29 | tests/e2e-tests/screenshots 30 | 31 | # docker data for local development 32 | docker/data 33 | docker/wordpress 34 | docker/logs 35 | docker/bin/jt 36 | .idea 37 | docker-compose.override.yml 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wp-calypso"] 2 | path = wp-calypso 3 | url = https://github.com/Automattic/wp-calypso 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | npm test 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.18.1 2 | -------------------------------------------------------------------------------- /assets/stylesheets/_colors.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-link: #984A9C; 3 | --color-error: #eb0001; 4 | --color-error-rgb: 235, 0, 1; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /assets/stylesheets/_fix-image-paths.scss: -------------------------------------------------------------------------------- 1 | // Fix paths in calypso css. Calypso updates have been known to rename files so we'll serve them locally instead of from wp.com 2 | input[type='checkbox'] { 3 | &:checked::before { 4 | content: url( '../../wp-calypso/static/images/checkbox-icons/checkmark-primary.svg' ); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/stylesheets/migration_to_wcshipping_admin_notice.scss: -------------------------------------------------------------------------------- 1 | // Manunally load the migration component's style sheet because it is bundled(webpack) 2 | // in style.css but style.css isn't loaded in the order listing page. 3 | // This .scss file will be loaded so we are including it here. 4 | @import 'components/migration/style'; 5 | 6 | .notice.wcst-wcshipping-migration-notice { 7 | padding: 12px; 8 | color: var(--Gutenberg-Gray-900, #1E1E1E); 9 | font-size: 13px; 10 | font-style: normal; 11 | font-weight: 400; 12 | line-height: 150%; 13 | 14 | .wcst-wcshipping-migration-notice__content { 15 | display: flex; 16 | flex-direction: row; 17 | margin-bottom: 12px; 18 | 19 | p { 20 | margin: 0; 21 | flex-grow: 1; 22 | } 23 | 24 | .notice-dismiss { 25 | width: 24px; 26 | height: 24px; 27 | padding: 0; 28 | position: relative; 29 | 30 | &:before { 31 | content: '\f335'; 32 | color: #1E1E1E; 33 | } 34 | } 35 | 36 | .hide-dismissible { 37 | display: none; 38 | } 39 | } 40 | 41 | .action-button { 42 | color: #3858E9; 43 | margin: 12px 0; 44 | border: 1px solid #3858E9; 45 | padding: 11.5px 12px 11.5px 12px; 46 | border-radius: 2px; 47 | background-color: #fff; 48 | cursor: pointer; 49 | margin: 0; 50 | } 51 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /bin/composer-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" 4 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 5 | ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" 6 | 7 | if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] 8 | then 9 | >&2 echo 'ERROR: Invalid installer checksum' 10 | rm composer-setup.php 11 | exit 1 12 | fi 13 | 14 | php composer-setup.php --quiet 15 | RESULT=$? 16 | rm composer-setup.php 17 | exit $RESULT -------------------------------------------------------------------------------- /bin/wc-phpcbf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # phpcbf returns exit-code as 1 even if all errors were fixed. 4 | # https://github.com/squizlabs/PHP_CodeSniffer/issues/3057#issuecomment-919794895 5 | 6 | composer run phpcbf $@ 7 | 8 | status=$? 9 | 10 | [ $status -eq 1 ] && exit 0 || exit $status -------------------------------------------------------------------------------- /classes/class-wc-connect-compatibility-wc30.php: -------------------------------------------------------------------------------- 1 | ID ); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /classes/class-wc-connect-compatibility-wc69.php: -------------------------------------------------------------------------------- 1 | settings_store = $settings_store; 23 | $this->service_schemas_store = $service_schemas_store; 24 | } 25 | public function get() { 26 | return array( 27 | 'storeOptions' => $this->settings_store->get_store_options(), 28 | 'formSchema' => array( 29 | 'custom' => $this->service_schemas_store->get_packages_schema(), 30 | 'predefined' => $this->service_schemas_store->get_predefined_packages_schema(), 31 | ), 32 | 'formData' => array( 33 | 'custom' => $this->settings_store->get_packages(), 34 | 'predefined' => $this->settings_store->get_predefined_packages(), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /classes/class-wc-connect-payment-gateway.php: -------------------------------------------------------------------------------- 1 | $value ) { 10 | $this->{$key} = $value; 11 | } 12 | 13 | $this->init_settings(); 14 | 15 | } 16 | 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /classes/class-wc-connect-wcst-to-wcshipping-migration-state-enum.php: -------------------------------------------------------------------------------- 1 | true, 20 | 'assets' => array( 21 | 'wc_connect_admin_script' => WC_Connect_Loader::get_wcs_admin_script_url(), 22 | 'wc_connect_admin_style' => WC_Connect_Loader::get_wcs_admin_style_url(), 23 | ), 24 | ), 25 | 200 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-self-help-controller.php: -------------------------------------------------------------------------------- 1 | get_json_params(); 16 | 17 | if ( 18 | empty( $settings ) 19 | || ! array_key_exists( 'wcc_debug_on', $settings ) 20 | || ! array_key_exists( 'wcc_logging_on', $settings ) 21 | ) { 22 | $error = new WP_Error( 23 | 'bad_form_data', 24 | __( 'Unable to update settings. The form data could not be read.', 'woocommerce-services' ), 25 | array( 'status' => 400 ) 26 | ); 27 | $this->logger->log( $error, __CLASS__ ); 28 | return $error; 29 | } 30 | 31 | if ( 1 == $settings['wcc_logging_on'] ) { 32 | $this->logger->enable_logging(); 33 | } else { 34 | $this->logger->disable_logging(); 35 | } 36 | 37 | if ( 1 == $settings['wcc_debug_on'] ) { 38 | $this->logger->enable_debug(); 39 | } else { 40 | $this->logger->disable_debug(); 41 | } 42 | 43 | return new WP_REST_Response( array( 'success' => true ), 200 ); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-service-data-refresh-controller.php: -------------------------------------------------------------------------------- 1 | services_schemas_store = $services_schemas_store; 21 | } 22 | 23 | public function post() { 24 | $result = $this->services_schemas_store->fetch_service_schemas_from_connect_server(); 25 | if ( $result === false ) { 26 | return new WP_REST_Response( 27 | [ 28 | 'success' => false, 29 | ], 30 | 500 31 | ); 32 | } 33 | 34 | $schemas = $this->services_schemas_store->get_service_schemas(); 35 | 36 | return new WP_REST_Response( 37 | [ 38 | 'success' => true, 39 | 'timestamp' => $this->services_schemas_store->get_last_fetch_timestamp(), 40 | 'has_service_schemas' => ! is_null( $schemas ), 41 | ], 42 | 200 43 | ); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-carrier-controller.php: -------------------------------------------------------------------------------- 1 | get_json_params(); 16 | 17 | $response = $this->api_client->create_shipping_carrier_account( $settings ); 18 | if ( is_wp_error( $response ) ) { 19 | $error = new WP_Error( 20 | $response->get_error_code(), 21 | $response->get_error_message(), 22 | array( 'message' => $response->get_error_message() ) 23 | ); 24 | $this->logger->log( $error, __CLASS__ ); 25 | 26 | return $error; 27 | } 28 | 29 | do_action( 'wc_connect_fetch_service_schemas' ); 30 | return $response; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-carrier-delete-controller.php: -------------------------------------------------------------------------------- 1 | .+)'; 13 | 14 | public function delete( $request ) { 15 | $carrier_id = $request['carrier_id']; 16 | 17 | $response = $this->api_client->disconnect_carrier_account( $carrier_id ); 18 | if ( is_wp_error( $response ) ) { 19 | $error = new WP_Error( 20 | $response->get_error_code(), 21 | $response->get_error_message(), 22 | array( 'message' => $response->get_error_message() ) 23 | ); 24 | $this->logger->log( $error, __CLASS__ ); 25 | return $error; 26 | } 27 | 28 | do_action( 'wc_connect_fetch_service_schemas' ); 29 | return array( 'success' => true ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-carrier-types-controller.php: -------------------------------------------------------------------------------- 1 | api_client->get_carrier_types(); 30 | if ( is_wp_error( $response ) ) { 31 | $this->logger->log( $response, __CLASS__ ); 32 | return $response; 33 | } 34 | return new WP_REST_Response( 35 | array( 36 | 'success' => true, 37 | 'carriers' => $response->carriers, 38 | ) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-carriers-controller.php: -------------------------------------------------------------------------------- 1 | api_client->get_all_shipping_carriers(); 16 | if ( is_wp_error( $response ) ) { 17 | $error = new WP_Error( 18 | $response->get_error_code(), 19 | $response->get_error_message(), 20 | array( 'message' => $response->get_error_message() ) 21 | ); 22 | $this->logger->log( $error, __CLASS__ ); 23 | return $error; 24 | } 25 | 26 | return $response; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-label-preview-controller.php: -------------------------------------------------------------------------------- 1 | get_params(); 16 | $params = array(); 17 | 18 | $params['paper_size'] = $raw_params['paper_size']; 19 | $this->settings_store->set_preferred_paper_size( $params['paper_size'] ); 20 | $params['carrier'] = 'usps'; 21 | $params['labels'] = array(); 22 | $captions = empty( $raw_params['caption_csv'] ) ? array() : explode( ',', $raw_params['caption_csv'] ); 23 | 24 | foreach ( $captions as $caption ) { 25 | $params['labels'][] = array( 'caption' => urldecode( $caption ) ); 26 | } 27 | 28 | $raw_response = $this->api_client->get_labels_preview_pdf( $params ); 29 | 30 | if ( is_wp_error( $raw_response ) ) { 31 | $this->logger->log( $raw_response, __CLASS__ ); 32 | return $raw_response; 33 | } 34 | 35 | header( 'content-type: ' . $raw_response['headers']['content-type'] ); 36 | echo $raw_response['body']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 37 | die(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-label-refund-controller.php: -------------------------------------------------------------------------------- 1 | \d+)/(?P\d+)/refund'; 13 | 14 | public function post( $request ) { 15 | $response = $this->api_client->send_shipping_label_refund_request( $request['label_id'] ); 16 | 17 | if ( isset( $response->error ) ) { 18 | $response = new WP_Error( 19 | property_exists( $response->error, 'code' ) ? $response->error->code : 'refund_error', 20 | property_exists( $response->error, 'message' ) ? $response->error->message : '' 21 | ); 22 | } 23 | 24 | if ( is_wp_error( $response ) ) { 25 | $response->add_data( 26 | array( 27 | 'message' => $response->get_error_message(), 28 | ), 29 | $response->get_error_code() 30 | ); 31 | 32 | $this->logger->log( $response, __CLASS__ ); 33 | return $response; 34 | } 35 | 36 | $label_refund = (object) array( 37 | 'label_id' => (int) $response->label->id, 38 | 'refund' => $response->refund, 39 | ); 40 | $this->settings_store->update_label_order_meta_data( $request['order_id'], $label_refund ); 41 | 42 | return array( 43 | 'success' => true, 44 | 'refund' => $response->refund, 45 | ); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-shipping-label-status-controller.php: -------------------------------------------------------------------------------- 1 | \d+)/(?P(\d+)(,\d+)*)'; 13 | 14 | public function get( $request ) { 15 | $label_ids = explode( ',', $request['label_ids'] ); 16 | $labels = array(); 17 | foreach ( $label_ids as $label_id ) { 18 | $response = $this->api_client->get_label_status( $label_id ); 19 | if ( is_wp_error( $response ) ) { 20 | $error = new WP_Error( 21 | $response->get_error_code(), 22 | $response->get_error_message(), 23 | array( 'message' => $response->get_error_message() ) 24 | ); 25 | $this->logger->log( $error, __CLASS__ ); 26 | return $error; 27 | } 28 | 29 | $label = $this->settings_store->update_label_order_meta_data( $request['order_id'], $response->label ); 30 | $labels[] = $label; 31 | } 32 | 33 | return array( 34 | 'success' => true, 35 | 'labels' => $labels, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-subscription-activate-controller.php: -------------------------------------------------------------------------------- 1 | .+)/activate'; 13 | 14 | public function post( $request ) { 15 | $subscription_key = $request['subscription_key']; 16 | 17 | $response = $this->api_client->activate_subscription( $subscription_key ); 18 | if ( is_wp_error( $response ) ) { 19 | $this->logger->log( $response, __CLASS__ ); 20 | return $response; 21 | } 22 | 23 | $activated = wp_remote_retrieve_response_code( $activation_response ) === 200; 24 | $body = json_decode( wp_remote_retrieve_body( $response ), true ); 25 | if ( ( ! $activated && ! empty( $body['code'] ) && 'already_connected' === $body['code'] ) ) { 26 | return new WP_Error( 27 | 'already_active', 28 | __( 'The subscription is already active.', 'woocommerce-services' ) 29 | ); 30 | } 31 | 32 | return new WP_REST_Response( 33 | array( 34 | 'success' => true, 35 | ) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-subscriptions-controller.php: -------------------------------------------------------------------------------- 1 | api_client->get_wccom_subscriptions(); 16 | if ( is_wp_error( $response ) ) { 17 | $this->logger->log( $response, __CLASS__ ); 18 | return $response; 19 | } 20 | 21 | return new WP_REST_Response( 22 | array( 23 | 'success' => true, 24 | 'subscriptions' => $response->subscriptions, 25 | ) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-tos-controller.php: -------------------------------------------------------------------------------- 1 | true, 19 | 'accepted' => WC_Connect_Options::get_option( 'tos_accepted' ), 20 | ), 21 | 200 22 | ); 23 | } 24 | 25 | public function post( $request ) { 26 | $settings = $request->get_json_params(); 27 | 28 | if ( ! $settings || ! isset( $settings['accepted'] ) || ! $settings['accepted'] ) { 29 | return new WP_Error( 'bad_request', __( 'Bad request', 'woocommerce-services' ), array( 'status' => 400 ) ); 30 | } 31 | 32 | WC_Connect_Options::update_option( 'tos_accepted', true ); 33 | 34 | return new WP_REST_Response( 35 | array( 36 | 'success' => true, 37 | 'accepted' => WC_Connect_Options::get_option( 'tos_accepted' ), 38 | ), 39 | 200 40 | ); 41 | } 42 | 43 | /** 44 | * Validate the requester's permissions 45 | */ 46 | public function check_permission( $request ) { 47 | return current_user_can( 'manage_woocommerce' ) && 48 | current_user_can( 'install_plugins' ) && 49 | current_user_can( 'activate_plugins' ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /classes/class-wc-rest-connect-wcshipping-compatibility-packages-controller.php: -------------------------------------------------------------------------------- 1 | { 9 | if ( jsonError.data.message ) { 10 | throw jsonError.data.message; 11 | } 12 | 13 | throw JSON.stringify( jsonError ); 14 | }; 15 | 16 | export const post = ( url, data ) => request().post( url, data ).catch( handleError ); 17 | 18 | export const get = ( url ) => request().get( url ).catch( handleError ); 19 | 20 | export const createGetUrlWithNonce = ( url, queryString ) => request().createGetUrlWithNonce( url, queryString ); 21 | -------------------------------------------------------------------------------- /client/api/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | const namespace = 'wc/v1/connect/'; 5 | 6 | export const labelsPrint = () => `${ namespace }label/print`; 7 | 8 | export const labelTestPrint = () => `${ namespace }label/preview`; 9 | 10 | export const selfHelp = () => `${ namespace }self-help`; 11 | 12 | export const refreshServiceData = () => `${ namespace }service-data-refresh`; 13 | -------------------------------------------------------------------------------- /client/apps/plugin-status/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import './style.scss'; 10 | import status from './state/reducer'; 11 | import View from './view'; 12 | // from calypso 13 | import notices from 'state/notices/reducer'; 14 | import { combineReducers } from 'state/utils'; 15 | 16 | export default ( { formData } ) => ( { 17 | getReducer() { 18 | return combineReducers( { 19 | status, 20 | notices, 21 | } ); 22 | }, 23 | 24 | getInitialState() { 25 | return { status: formData }; 26 | }, 27 | 28 | getStateForPersisting() { 29 | return null; 30 | }, 31 | 32 | getStateKey() { 33 | return 'wcs-admin-status'; 34 | }, 35 | 36 | View: () => ( 37 | 38 | ), 39 | } ); 40 | -------------------------------------------------------------------------------- /client/apps/plugin-status/state/action-types.js: -------------------------------------------------------------------------------- 1 | export const PLUGIN_STATUS_DEBUG_TOGGLE = 'PLUGIN_STATUS_DEBUG_TOGGLE'; 2 | export const PLUGIN_STATUS_LOGGING_TOGGLE = 'PLUGIN_STATUS_LOGGING_TOGGLE'; 3 | export const SERVICE_DATA_REFRESH = 'SERVICE_DATA_REFRESH'; 4 | -------------------------------------------------------------------------------- /client/apps/plugin-status/state/reducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { 5 | PLUGIN_STATUS_DEBUG_TOGGLE, 6 | PLUGIN_STATUS_LOGGING_TOGGLE, 7 | SERVICE_DATA_REFRESH, 8 | } from './action-types'; 9 | 10 | const reducer = { 11 | [ PLUGIN_STATUS_DEBUG_TOGGLE ]: ( state, { value } ) => { 12 | return { 13 | ...state, 14 | debug_enabled: value, 15 | }; 16 | }, 17 | 18 | [ PLUGIN_STATUS_LOGGING_TOGGLE ]: ( state, { value } ) => { 19 | return { 20 | ...state, 21 | logging_enabled: value, 22 | }; 23 | }, 24 | 25 | [ SERVICE_DATA_REFRESH ]: ( state, { value } ) => { 26 | return { 27 | ...state, 28 | health_items: { 29 | ...state.health_items, 30 | woocommerce_services: { 31 | ...state.health_items.woocommerce_services, 32 | ...value, 33 | } 34 | } 35 | }; 36 | }, 37 | }; 38 | 39 | export default ( state = {}, action ) => { 40 | if ( reducer[ action.type ] ) { 41 | return reducer[ action.type ]( state, action ); 42 | } 43 | 44 | return state; 45 | }; 46 | -------------------------------------------------------------------------------- /client/apps/print-test-label/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import './style.scss'; 10 | import PrintTestLabelView from './view'; 11 | import reducer from './state/reducer'; 12 | 13 | export default ( { paperSize, storeOptions, isShippingLoaded } ) => ( { 14 | getReducer() { 15 | return reducer; 16 | }, 17 | 18 | getInitialState() { 19 | return { 20 | paperSize, 21 | country: storeOptions.origin_country, 22 | isShippingLoaded, 23 | }; 24 | }, 25 | 26 | getStateKey() { 27 | return 'wcs-print-test-label'; 28 | }, 29 | 30 | View: () => { 31 | return ; 32 | }, 33 | } ); 34 | -------------------------------------------------------------------------------- /client/apps/print-test-label/state/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { translate as __ } from 'i18n-calypso'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import { getPreviewURL } from 'lib/pdf-label-utils'; 10 | import printDocument from 'lib/utils/print-document'; 11 | 12 | export const UPDATE_PAPER_SIZE = 'UPDATE_PAPER_SIZE'; 13 | export const PRINTING_IN_PROGRESS = 'PRINTING_IN_PROGRESS'; 14 | export const PRINTING_ERROR = 'PRINTING_ERROR'; 15 | 16 | export const updatePaperSize = ( paperSize ) => { 17 | return { type: UPDATE_PAPER_SIZE, paperSize }; 18 | }; 19 | 20 | export const print = () => ( dispatch, getState ) => { 21 | dispatch( { type: PRINTING_IN_PROGRESS, inProgress: true } ); 22 | const { paperSize } = getState(); 23 | 24 | const labelData = [ 25 | { caption: __( 'TEST LABEL 1' ) }, 26 | ]; 27 | if ( 'label' !== paperSize ) { 28 | labelData.push( { caption: __( 'TEST LABEL 2' ) } ); 29 | } 30 | 31 | printDocument( getPreviewURL( paperSize, labelData ) ) 32 | .then( () => { 33 | dispatch( { type: PRINTING_IN_PROGRESS, inProgress: false } ); 34 | } ) 35 | .catch( ( error ) => { 36 | dispatch( { type: PRINTING_ERROR, error } ); 37 | } ); 38 | }; 39 | -------------------------------------------------------------------------------- /client/apps/print-test-label/state/reducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { UPDATE_PAPER_SIZE, PRINTING_IN_PROGRESS, PRINTING_ERROR } from './actions'; 5 | 6 | const reducers = {}; 7 | 8 | reducers[ UPDATE_PAPER_SIZE ] = ( state, { paperSize } ) => { 9 | return { ...state, paperSize }; 10 | }; 11 | 12 | reducers[ PRINTING_IN_PROGRESS ] = ( state, { inProgress } ) => { 13 | const newState = { ...state, 14 | printingInProgress: inProgress, 15 | }; 16 | if ( inProgress ) { 17 | delete newState.error; 18 | } 19 | return newState; 20 | }; 21 | 22 | reducers[ PRINTING_ERROR ] = ( state, { error } ) => { 23 | return { ...state, 24 | printingInProgress: false, 25 | error: error.toString(), 26 | }; 27 | }; 28 | 29 | export default ( state = {}, action ) => { 30 | if ( reducers[ action.type ] ) { 31 | return reducers[ action.type ]( state, action ); 32 | } 33 | return state; 34 | }; 35 | -------------------------------------------------------------------------------- /client/apps/print-test-label/style.scss: -------------------------------------------------------------------------------- 1 | .print-test-label__form-container { 2 | display: flex; 3 | 4 | .print-test-label__paper-size { 5 | width: auto; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client/apps/settings/style.scss: -------------------------------------------------------------------------------- 1 | .settings__placeholder { 2 | .form-label, 3 | .form-text-input { 4 | @include placeholder(); 5 | pointer-events: none; 6 | } 7 | } 8 | 9 | .settings__button-row { 10 | background: var( --color-surface-backdrop ); 11 | padding: 16px 24px; 12 | margin-right: 0; 13 | margin-left: 0; 14 | max-width: 100%; 15 | } 16 | -------------------------------------------------------------------------------- /client/apps/shipping-label/redux-middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | * External dependencies 3 | */ 4 | import jQuery from 'jquery'; 5 | /** 6 | * Internal dependencies 7 | */ 8 | //from calypso 9 | import { 10 | WOOCOMMERCE_SERVICES_SHIPPING_LABEL_PURCHASE_RESPONSE, 11 | WOOCOMMERCE_SERVICES_SHIPPING_LABEL_OPEN_PRINTING_FLOW, 12 | } from '../../extensions/woocommerce/woocommerce-services/state/action-types'; 13 | 14 | const middlewareActions = { 15 | [ WOOCOMMERCE_SERVICES_SHIPPING_LABEL_PURCHASE_RESPONSE ]: ( { error } ) => { 16 | if ( error ) { 17 | return; 18 | } 19 | window.wc_shipment_tracking_refresh && window.wc_shipment_tracking_refresh(); 20 | }, 21 | [ WOOCOMMERCE_SERVICES_SHIPPING_LABEL_OPEN_PRINTING_FLOW ]: () => { 22 | //dismiss the nux pointer if the user opens the printing flow 23 | const labelMetabox = jQuery( '#woocommerce-order-label' ); 24 | if ( labelMetabox.pointer ) { 25 | labelMetabox.pointer().pointer( 'close' ); 26 | } 27 | }, 28 | }; 29 | 30 | export default () => ( next ) => ( action ) => { 31 | // let the action go to the reducers 32 | next( action ); 33 | 34 | const middlewareAction = middlewareActions[ action.type ]; 35 | if ( ! middlewareAction ) { 36 | return; 37 | } 38 | 39 | // perform the action 40 | middlewareAction( action ); 41 | }; 42 | -------------------------------------------------------------------------------- /client/banner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import jQuery from 'jquery'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import '../assets/stylesheets/banner.scss'; 10 | 11 | jQuery( document ).ready( ( $ ) => { 12 | $( '.woocommerce-services__connect-jetpack' ).one( 'click', function() { 13 | const btn = $( this ); 14 | btn.addClass( 'disabled' ); 15 | } ); 16 | } ); 17 | -------------------------------------------------------------------------------- /client/calypso-stubs/components/data/query-stored-cards/index.js: -------------------------------------------------------------------------------- 1 | export default () => null; 2 | -------------------------------------------------------------------------------- /client/calypso-stubs/config.js: -------------------------------------------------------------------------------- 1 | /* Required by some wp-calypso components. */ 2 | /*global process */ 3 | 4 | const data = { 5 | env: ( process && process.env.NODE_ENV ) || 'development', 6 | wpcom_concierge_schedule_id: 1, 7 | languages: [], 8 | google_adwords_conversion_id: '', 9 | google_adwords_conversion_id_jetpack: '', 10 | }; 11 | 12 | const d = ( key ) => { 13 | if ( key in data ) { 14 | return data[ key ]; 15 | } 16 | throw new Error( 'config key `' + key + '` does not exist' ); 17 | }; 18 | 19 | d.isEnabled = () => true; 20 | 21 | export default d; 22 | 23 | export const isEnabled = () => true; 24 | export const anyEnabled = () => true; 25 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/lib/nav-utils.js: -------------------------------------------------------------------------------- 1 | export const getOrigin = () => { 2 | return 'https://wordpress.com'; 3 | }; 4 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/state/selectors/plugins.js: -------------------------------------------------------------------------------- 1 | export const isWcsEnabled = () => true; 2 | 3 | export const isWcsInternationalLabelsEnabled = () => true; 4 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/state/sites/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import def from 'api/request'; 5 | 6 | export * from 'api/request'; 7 | export default def; 8 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/woocommerce-services/lib/utils/get-packaging-manager-link.js: -------------------------------------------------------------------------------- 1 | export default () => 'admin.php?page=wc-settings&tab=shipping§ion=woocommerce-services-settings'; 2 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/woocommerce-services/lib/utils/get-product-link.js: -------------------------------------------------------------------------------- 1 | export default ( productId ) => { 2 | return `post.php?post=${ productId }&action=edit`; 3 | }; 4 | -------------------------------------------------------------------------------- /client/calypso-stubs/extensions/woocommerce/woocommerce-services/views/label-settings/add-credit-card-modal.js: -------------------------------------------------------------------------------- 1 | export default () => null; 2 | -------------------------------------------------------------------------------- /client/calypso-stubs/state/data-layer/wpcom-http/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stubs https://github.com/Automattic/wp-calypso/blob/52cfffdb51c915be3ca0e40413b82bbbee452573/client/state/data-layer/wpcom-http/index.js 3 | * Only exports the functions necessary for http-handlers to work 4 | */ 5 | 6 | export const successMeta = ( data, headers ) => ( { meta: { dataLayer: { data, headers } } } ); 7 | export const failureMeta = ( error, headers ) => ( { meta: { dataLayer: { error, headers } } } ); 8 | -------------------------------------------------------------------------------- /client/calypso-stubs/state/selectors/is-rtl.js: -------------------------------------------------------------------------------- 1 | export default () => document.body.classList.contains( 'rtl' ); 2 | -------------------------------------------------------------------------------- /client/calypso-stubs/state/sites/selectors.js: -------------------------------------------------------------------------------- 1 | export const getSite = () => ( {} ); 2 | export const getSiteSlug = () => ''; 3 | export const isJetpackSite = () => true; 4 | -------------------------------------------------------------------------------- /client/calypso-stubs/state/stored-cards/selectors.js: -------------------------------------------------------------------------------- 1 | export const getStoredCards = () => []; 2 | export const hasLoadedStoredCardsFromServer = () => false; 3 | -------------------------------------------------------------------------------- /client/components/error-notice/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react'; 5 | import PropTypes from 'prop-types'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import Notice from 'components/notice'; 11 | 12 | const ErrorNotice = ( { children, isWarning } ) => { 13 | return ( 14 | 17 | { children } 18 | 19 | ); 20 | }; 21 | 22 | ErrorNotice.propTypes = { 23 | isWarning: PropTypes.bool, 24 | }; 25 | 26 | export default ErrorNotice; 27 | -------------------------------------------------------------------------------- /client/components/forms/form-button/style.scss: -------------------------------------------------------------------------------- 1 | .form-button { 2 | float: right; 3 | margin-left: 10px; 4 | 5 | &:first-child { 6 | margin-left: 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/components/forms/form-fieldset/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | 5 | import React from 'react'; 6 | import classnames from 'classnames'; 7 | 8 | const FormFieldset = ( { className, children, ...otherProps } ) => ( 9 |
10 | { children } 11 |
12 | ); 13 | 14 | export default FormFieldset; 15 | -------------------------------------------------------------------------------- /client/components/forms/form-fieldset/style.scss: -------------------------------------------------------------------------------- 1 | .form-fieldset { 2 | clear: both; 3 | margin-bottom: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /client/components/forms/form-label/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import classnames from 'classnames'; 10 | import { localize } from 'i18n-calypso'; 11 | 12 | const FormLabel = ( { children: childrenProp, required, optional, translate, className, moment, numberFormat, dangerouslySetInnerHTML, ...extraProps } ) => { 13 | const children = dangerouslySetInnerHTML ? null : ( 14 | <> 15 | { childrenProp } 16 | {/* eslint-disable-next-line wpcalypso/jsx-classname-namespace */} 17 | { required && ( { translate( 'Required' ) } ) } 18 | {/* eslint-disable-next-line wpcalypso/jsx-classname-namespace */} 19 | { optional && ( { translate( 'Optional' ) } ) } 20 | 21 | ); 22 | 23 | return ( 24 | 32 | ); 33 | }; 34 | 35 | FormLabel.propTypes = { 36 | required: PropTypes.bool, 37 | optional: PropTypes.bool, 38 | children: PropTypes.node, 39 | }; 40 | 41 | export default localize( FormLabel ); 42 | -------------------------------------------------------------------------------- /client/components/forms/form-label/style.scss: -------------------------------------------------------------------------------- 1 | .form-label { 2 | display: block; 3 | font-size: 14px; 4 | font-weight: 600; 5 | margin-bottom: 5px; 6 | 7 | .form-label__required { 8 | color: var( --color-error ); 9 | font-weight: normal; 10 | margin-left: 6px; 11 | } 12 | 13 | .form-label__optional { 14 | color: var( --color-neutral-500 ); 15 | font-weight: normal; 16 | margin-left: 6px; 17 | } 18 | } 19 | 20 | .form-label input[type='checkbox'] + span, 21 | .form-label input[type='radio'] + span { 22 | font-weight: normal; 23 | } 24 | -------------------------------------------------------------------------------- /client/components/forms/form-legend/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react'; 8 | import classnames from 'classnames'; 9 | 10 | const FormLegend = ( { className, children, ...otherProps } ) => ( 11 | 12 | { children } 13 | 14 | ); 15 | 16 | export default FormLegend; 17 | -------------------------------------------------------------------------------- /client/components/forms/form-legend/style.scss: -------------------------------------------------------------------------------- 1 | .form-legend { 2 | font-size: 14px; 3 | font-weight: 600; 4 | margin-bottom: 5px; 5 | } 6 | li .form-legend { 7 | margin-top: 4px; 8 | } 9 | -------------------------------------------------------------------------------- /client/components/forms/form-radio/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react'; 8 | import classnames from 'classnames'; 9 | 10 | const FormRadio = ( { className, ...otherProps } ) => ( 11 | 12 | ); 13 | 14 | export default FormRadio; 15 | -------------------------------------------------------------------------------- /client/components/forms/form-section-heading/index.jsx: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react'; 8 | import classnames from 'classnames'; 9 | 10 | const FormSectionHeading = ( { className, children, ...otherProps } ) => ( 11 |

12 | { children } 13 |

14 | ); 15 | 16 | export default FormSectionHeading; 17 | -------------------------------------------------------------------------------- /client/components/forms/form-section-heading/style.scss: -------------------------------------------------------------------------------- 1 | .form-section-heading { 2 | font-size: 24px; 3 | font-weight: 300; 4 | margin: 30px 0 20px; 5 | } 6 | 7 | .form-section-heading:first-child { 8 | margin-top: 0; 9 | } 10 | -------------------------------------------------------------------------------- /client/components/forms/form-select/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react' 8 | import classNames from 'classnames' 9 | import './styles.scss' 10 | 11 | const FormSelect = ({ inputRef, className, isError, ...restProps }) => { 12 | return ( 13 | 20 | { checked && } 21 | { ! checked && partialChecked && } 22 | 23 | ); 24 | }; 25 | 26 | Checkbox.propTypes = { 27 | checked: PropTypes.bool.isRequired, 28 | partialChecked: PropTypes.bool, 29 | onChange: PropTypes.func.isRequired, 30 | className: PropTypes.string, 31 | }; 32 | 33 | export default Checkbox; 34 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/checkbox/style.scss: -------------------------------------------------------------------------------- 1 | .form-checkbox { 2 | margin-right: 8px; 3 | cursor: pointer; 4 | display: inline-flex; 5 | align-items: center; 6 | position: relative; 7 | vertical-align: text-bottom; 8 | 9 | input { 10 | margin: 0; 11 | } 12 | 13 | .gridicon { 14 | color: var( --color-primary ); 15 | pointer-events: none; 16 | position: absolute; 17 | 18 | &.gridicons-checkmark { 19 | left: 1px; 20 | top: 1px; 21 | } 22 | } 23 | 24 | &.is-disabled .gridicon { 25 | color: var( --color-neutral-200 ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/field-description/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | import FormSettingExplanation from 'wcs-client/components/forms/form-setting-explanation'; 13 | import sanitizeHTML from 'woocommerce/woocommerce-services/lib/utils/sanitize-html'; 14 | 15 | const FieldDescription = ( { text } ) => { 16 | return text ? : null; 17 | }; 18 | 19 | FieldDescription.propTypes = { 20 | text: PropTypes.string, 21 | }; 22 | 23 | export default FieldDescription; 24 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/field-error/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import classNames from 'classnames'; 10 | import Gridicon from 'gridicons'; 11 | 12 | const FieldError = ( { text, type = 'input-validation' } ) => { 13 | return ( 14 |
15 | { text } 16 |
17 | ); 18 | }; 19 | 20 | FieldError.propTypes = { 21 | text: PropTypes.string.isRequired, 22 | type: PropTypes.string, 23 | }; 24 | 25 | export default FieldError; 26 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/field-error/style.scss: -------------------------------------------------------------------------------- 1 | .field-error { 2 | color: var( --color-error ); 3 | } 4 | 5 | .field-error__input-validation { 6 | padding: 4px 0; 7 | .gridicon { 8 | float: none; 9 | vertical-align: middle; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/info-tooltip/style.scss: -------------------------------------------------------------------------------- 1 | .components-popover__content { 2 | font-size: 12px; 3 | font-weight: normal; 4 | } 5 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/query-packages/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | import { bindActionCreators } from 'redux'; 10 | 11 | /** 12 | * Internal dependencies 13 | */ 14 | import { fetchSettings } from 'woocommerce/woocommerce-services/state/packages/actions'; 15 | import { 16 | isLoaded, 17 | isFetching, 18 | isFetchError, 19 | } from 'woocommerce/woocommerce-services/state/packages/selectors'; 20 | 21 | class QueryPackages extends Component { 22 | fetch( props ) { 23 | const { siteId, loaded, fetching, error } = props; 24 | if ( ! loaded && ! fetching && ! error ) { 25 | this.props.fetchSettings( siteId ); 26 | } 27 | } 28 | 29 | UNSAFE_componentWillMount() { 30 | this.fetch( this.props ); 31 | } 32 | 33 | UNSAFE_componentWillReceiveProps( nextProps ) { 34 | this.fetch( nextProps ); 35 | } 36 | 37 | render() { 38 | return null; 39 | } 40 | } 41 | 42 | QueryPackages.propTypes = { 43 | siteId: PropTypes.number.isRequired, 44 | }; 45 | 46 | export default connect( 47 | state => ( { 48 | loaded: isLoaded( state ), 49 | fetching: isFetching( state ), 50 | error: isFetchError( state ), 51 | } ), 52 | dispatch => 53 | bindActionCreators( 54 | { 55 | fetchSettings, 56 | }, 57 | dispatch 58 | ) 59 | )( QueryPackages ); 60 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/settings-group-card/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import classnames from 'classnames'; 9 | import { Card } from '@wordpress/components'; 10 | 11 | /** 12 | * Internal dependencies 13 | */ 14 | import FormSectionHeading from 'wcs-client/components/forms/form-section-heading'; 15 | 16 | const SettingsGroupCard = ( { heading, children } ) => { 17 | return ( 18 | 19 | { heading && ( 20 | 21 | { heading } 22 | 23 | ) } 24 |
27 | { children } 28 |
29 |
30 | ); 31 | }; 32 | 33 | SettingsGroupCard.propTypes = { 34 | heading: PropTypes.node, 35 | }; 36 | 37 | export default SettingsGroupCard; 38 | 39 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/settings-group-card/style.scss: -------------------------------------------------------------------------------- 1 | .card.is-compact { 2 | &.settings-group-card { 3 | margin-left: 0; 4 | margin-right: 0; 5 | max-width: 100%; 6 | display: flex; 7 | padding-top: 24px; 8 | padding-bottom: 24px; 9 | 10 | @include breakpoint( '<660px' ) { 11 | flex-wrap: wrap; 12 | } 13 | } 14 | } 15 | 16 | .settings-group-card__heading { 17 | font-size: 16px; 18 | font-weight: 600; 19 | color: var( --color-neutral-500 ); 20 | width: 22%; 21 | padding-right: 10px; 22 | box-sizing: border-box; 23 | @include breakpoint( '<660px' ) { 24 | width: 100%; 25 | } 26 | } 27 | 28 | .settings-group-card__content { 29 | width: 78%; 30 | @include breakpoint( '<660px' ) { 31 | width: 100%; 32 | } 33 | 34 | .is-full-width { 35 | width: 100%; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/text-field/style.scss: -------------------------------------------------------------------------------- 1 | .form-text-input { 2 | &.is-error { 3 | input { 4 | color: var( --color-error ); 5 | border-color: var( --color-error ); 6 | } 7 | } 8 | 9 | .components-base-control__label { 10 | display: block; 11 | font-size: 14px; 12 | font-weight: 600; 13 | margin-bottom: 5px; 14 | } 15 | } -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/text/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /* eslint-disable react/no-danger */ 4 | /** 5 | * External dependencies 6 | */ 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | /** 11 | * Internal dependencies 12 | */ 13 | import FormFieldset from 'components/forms/form-fieldset'; 14 | import FormLegend from 'wcs-client/components/forms/form-legend'; 15 | import sanitizeHTML from 'woocommerce/woocommerce-services/lib/utils/sanitize-html'; 16 | 17 | const renderTitle = title => { 18 | if ( ! title ) { 19 | return null; 20 | } 21 | 22 | return ; 23 | }; 24 | 25 | const renderText = text => { 26 | return ; 27 | }; 28 | 29 | const Text = ( { id, title, className, value } ) => { 30 | return ( 31 | 32 | { renderTitle( title ) } 33 |

34 | { renderText( value ) } 35 |

36 |
37 | ); 38 | }; 39 | 40 | Text.propTypes = { 41 | id: PropTypes.string.isRequired, 42 | title: PropTypes.string, 43 | className: PropTypes.string, 44 | value: PropTypes.string.isRequired, 45 | }; 46 | 47 | export default Text; 48 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/components/text/style.scss: -------------------------------------------------------------------------------- 1 | .form-text-body-copy { 2 | font-size: 14px; 3 | font-weight: 400; 4 | line-height: 1.5; 5 | } 6 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/get-default-settings-values/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { get, mapValues } from 'lodash'; 7 | 8 | function getItemValue( schema ) { 9 | switch ( schema.type ) { 10 | case 'boolean': 11 | return schema.default || false; 12 | case 'number': 13 | return schema.default || 0; 14 | case 'string': 15 | case 'textarea': 16 | return schema.default || ''; 17 | case 'array': 18 | return schema.default || []; 19 | case 'object': 20 | return mapValues( get( schema, 'properties', {} ), getItemValue ); 21 | default: 22 | return null; 23 | } 24 | } 25 | 26 | export default schema => mapValues( schema.properties, getItemValue ); 27 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/get-address-values.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { isPlainObject } from 'lodash'; 7 | 8 | /** 9 | * @param {object} addressData Object representing the current address UI state. 10 | * - {object} addressData.values The address values, as entered by the user. 11 | * - {object} addressData.normalized The address values, if any, as returned by the address verification endpoint. 12 | * - {boolean} addressData.isNormalized True if the address has been normalized, false otherwise. 13 | * - {boolean} addressData.selectNormalized True if the user has chosen to accept the normalized address, false otherwise. 14 | * @returns {object} Object with the selected address values (postcode, country, state, etc), or an empty object on error. 15 | */ 16 | export default addressData => { 17 | if ( ! isPlainObject( addressData ) ) { 18 | return {}; 19 | } 20 | const selectedValues = 21 | addressData.isNormalized && addressData.selectNormalized && addressData.normalized 22 | ? addressData.normalized 23 | : addressData.values; 24 | return selectedValues || {}; 25 | }; 26 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/get-box-dimensions.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default box => { 4 | let dimensions; 5 | if ( box.outer_dimensions ) { 6 | dimensions = box.outer_dimensions.match( /([-.0-9]+).+?([-.0-9]+).+?([-.0-9]+)/ ); 7 | } else { 8 | dimensions = box.inner_dimensions.match( /([-.0-9]+).+?([-.0-9]+).+?([-.0-9]+)/ ); 9 | } 10 | 11 | const [ length, width, height ] = dimensions.slice( 1 ).map( Number ); 12 | 13 | return { length, width, height }; 14 | }; 15 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/parse-number.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default value => { 4 | if ( '' === value ) { 5 | return 0; 6 | } 7 | const float = Number.parseFloat( value ); 8 | return isNaN( float ) ? value : float; 9 | }; 10 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/sanitize-html.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { sanitize } from 'dompurify'; 7 | 8 | export const ALLOWED_TAGS = [ 'a', 'strong', 'em', 'u', 'tt', 's' ]; 9 | export const ALLOWED_ATTR = [ 'target', 'href' ]; 10 | 11 | export default html => { 12 | return { 13 | __html: sanitize( html, { ALLOWED_TAGS, ALLOWED_ATTR } ), 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/test/sanitize-html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | * @jest-environment jsdom 4 | */ 5 | 6 | /** 7 | * External dependencies 8 | */ 9 | import dompurify from 'dompurify'; 10 | 11 | /** 12 | * Internal dependencies 13 | */ 14 | import sanitizeHtml, { ALLOWED_TAGS, ALLOWED_ATTR } from '../sanitize-html'; 15 | 16 | describe( 'sanitizeHtml', () => { 17 | const html = `Link`; 18 | 19 | test( 'should remove any html tags and attributes which are not allowed', () => { 20 | expect( sanitizeHtml( html ) ).toEqual( { 21 | __html: 'Link', 22 | } ); 23 | } ); 24 | 25 | test( 'should call dompurify.sanitize with list of allowed tags and attributes', () => { 26 | const sanitizeMock = jest.spyOn( dompurify, 'sanitize' ); 27 | 28 | sanitizeHtml( html ); 29 | expect( sanitizeMock ).toHaveBeenCalledWith( html, { ALLOWED_ATTR, ALLOWED_TAGS } ); 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/lib/utils/tree.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { isArray, isPlainObject, some, values } from 'lodash'; 7 | 8 | /** 9 | * Checks if the given tree-like structure has any non-empty terminal nodes. 10 | * @param {object} tree Root of a tree-like object, can with each node being an object, an array, or a literal value 11 | * @returns {boolean} True if the tree has some non-empty terminal nodes, false otherwise 12 | */ 13 | export const hasNonEmptyLeaves = tree => { 14 | if ( ! tree ) { 15 | return false; 16 | } 17 | if ( isArray( tree ) ) { 18 | return some( tree, hasNonEmptyLeaves ); 19 | } 20 | if ( isPlainObject( tree ) ) { 21 | return some( values( tree ), hasNonEmptyLeaves ); 22 | } 23 | return true; 24 | }; 25 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/actions.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | /** 3 | * Internal dependencies 4 | */ 5 | import { WOOCOMMERCE_SERVICES_SHIPPING_ACTION_LIST_CREATE } from 'woocommerce/state/action-types'; 6 | 7 | /** 8 | * Creates an action list to save WCS shipping settings (labels and packages) 9 | * 10 | * Saves the WCS settings 11 | * @param {Function} [successAction] Action to be dispatched upon successful completion. 12 | * @param {Function} [failureAction] Action to be dispatched upon failure of execution. 13 | * @param {Boolean} [onlyPackages] Whether to just update packages or not (component from settings is re-used in order view). 14 | * @return {Object} Action object. 15 | */ 16 | export function createWcsShippingSaveActionList( 17 | successAction, 18 | failureAction, 19 | onlyPackages 20 | ) { 21 | return { 22 | type: WOOCOMMERCE_SERVICES_SHIPPING_ACTION_LIST_CREATE, 23 | successAction, 24 | failureAction, 25 | onlyPackages: onlyPackages || false, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/packages/test/data/initial-state.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | export default { 3 | showModal: false, 4 | packageData: null, 5 | packages: { 6 | custom: [ { name: '1' }, { name: '2' }, { name: 'zBox' } ], 7 | predefined: { service: [ 'box', 'box1' ], otherService: [ 'envelope' ] }, 8 | }, 9 | predefinedSchema: { 10 | service: { 11 | priority: { 12 | definitions: [ 13 | { id: 'box', name: 'bBox', can_ship_international: true }, 14 | { id: 'box1', name: 'aBox', can_ship_international: false }, 15 | { id: 'box2', name: 'cBox', can_ship_international: true }, 16 | ], 17 | title: 'Priority', 18 | }, 19 | }, 20 | otherService: { 21 | express: { 22 | definitions: [ { id: 'envelope', name: 'envelope', can_ship_international: false } ], 23 | title: 'Express', 24 | }, 25 | }, 26 | }, 27 | pristine: true, 28 | isSaving: false, 29 | }; 30 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/service-settings/actions.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * Internal dependencies 5 | */ 6 | import { 7 | WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_UPDATE, 8 | WOOCOMMERCE_SERVICES_SERVICE_SETTINGS_UPDATE_FIELD, 9 | } from '../action-types'; 10 | 11 | export const updateWcsShippingZoneMethod = ( 12 | siteId, 13 | methodId, 14 | methodType, 15 | method, 16 | successAction, 17 | failureAction 18 | ) => ( { 19 | type: WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_UPDATE, 20 | siteId, 21 | methodId, 22 | methodType, 23 | method, 24 | successAction, 25 | failureAction, 26 | } ); 27 | 28 | export const updateField = ( siteId, methodId, path, value ) => ( { 29 | type: WOOCOMMERCE_SERVICES_SERVICE_SETTINGS_UPDATE_FIELD, 30 | siteId, 31 | methodId, 32 | // Since all the WCS shipping methods use the same reducer, methodType just needs to be a valid WCS method identifier. 33 | // This works for Canada Post and FedEx too. 34 | methodType: 'wc_services_usps', 35 | path, 36 | value, 37 | } ); 38 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/service-settings/reducer.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { setWith, clone } from 'lodash'; 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | import { WOOCOMMERCE_SERVICES_SERVICE_SETTINGS_UPDATE_FIELD } from '../action-types'; 12 | 13 | const reducers = {}; 14 | 15 | reducers[ WOOCOMMERCE_SERVICES_SERVICE_SETTINGS_UPDATE_FIELD ] = ( state, action ) => { 16 | return setWith( clone( state ), action.path, action.value, clone ); 17 | }; 18 | 19 | const settings = ( state = {}, action ) => { 20 | if ( 'function' === typeof reducers[ action.type ] ) { 21 | return reducers[ action.type ]( state, action ); 22 | } 23 | return state; 24 | }; 25 | 26 | export default settings; 27 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/shipping-label/constants.js: -------------------------------------------------------------------------------- 1 | // "Countries" from when USPS can ship a package. That is, the countries or territories with at least 1 USPS post office 2 | export const ACCEPTED_USPS_ORIGIN_COUNTRIES = [ 3 | 'US', // United States 4 | 'PR', // Puerto Rico 5 | 'VI', // Virgin Islands 6 | 'GU', // Guam 7 | 'AS', // American Samoa 8 | 'UM', // United States Minor Outlying Islands 9 | 'MH', // Marshall Islands 10 | 'FM', // Micronesia 11 | 'MP', // Northern Mariana Islands 12 | ]; 13 | 14 | // Packages shipping to or from the US, Puerto Rico and Virgin Islands don't need a Customs form 15 | export const DOMESTIC_US_TERRITORIES = [ 'US', 'PR', 'VI' ]; 16 | 17 | // These US states are a special case because they represent military bases. They're considered "domestic", 18 | // but they require a Customs form to ship from/to them. 19 | export const US_MILITARY_STATES = [ 'AA', 'AE', 'AP' ]; 20 | 21 | // These destination countries require an ITN regardless of shipment value 22 | export const USPS_ITN_REQUIRED_DESTINATIONS = [ 'IR', 'SY', 'KP', 'CU', 'SD' ]; 23 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/shipping-method-schemas/reducer.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * Internal dependencies 5 | */ 6 | 7 | import { createReducer, keyedReducer } from 'state/utils'; 8 | import { 9 | WOOCOMMERCE_SERVICES_SHIPPING_METHOD_SCHEMA_REQUEST, 10 | WOOCOMMERCE_SERVICES_SHIPPING_METHOD_SCHEMA_REQUEST_SUCCESS, 11 | } from 'woocommerce/woocommerce-services/state/action-types'; 12 | import { LOADING } from 'woocommerce/state/constants'; 13 | 14 | const reducer = createReducer( null, { 15 | [ WOOCOMMERCE_SERVICES_SHIPPING_METHOD_SCHEMA_REQUEST ]: () => { 16 | return LOADING; 17 | }, 18 | 19 | [ WOOCOMMERCE_SERVICES_SHIPPING_METHOD_SCHEMA_REQUEST_SUCCESS ]: ( state, { data } ) => { 20 | return data; 21 | }, 22 | } ); 23 | 24 | export default keyedReducer( 'methodId', reducer ); 25 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/state/shipping-zone-method-settings/reducer.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * Internal dependencies 5 | */ 6 | 7 | import { createReducer } from 'state/utils'; 8 | import { 9 | WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_SETTINGS_REQUEST, 10 | WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_SETTINGS_REQUEST_SUCCESS, 11 | } from 'woocommerce/woocommerce-services/state/action-types'; 12 | import { LOADING } from 'woocommerce/state/constants'; 13 | import { WOOCOMMERCE_SHIPPING_ZONE_METHOD_UPDATED } from 'woocommerce/state/action-types'; 14 | 15 | const reducers = {}; 16 | 17 | reducers[ WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_SETTINGS_REQUEST ] = ( 18 | state, 19 | { instanceId } 20 | ) => { 21 | return { 22 | ...state, 23 | [ instanceId ]: LOADING, 24 | }; 25 | }; 26 | 27 | reducers[ WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_SETTINGS_REQUEST_SUCCESS ] = ( 28 | state, 29 | { instanceId } 30 | ) => { 31 | return { 32 | ...state, 33 | [ instanceId ]: true, 34 | }; 35 | }; 36 | 37 | reducers[ WOOCOMMERCE_SHIPPING_ZONE_METHOD_UPDATED ] = ( state, { data } ) => { 38 | return reducers[ WOOCOMMERCE_SERVICES_SHIPPING_ZONE_METHOD_SETTINGS_REQUEST_SUCCESS ]( state, { 39 | instanceId: data.id, 40 | } ); 41 | }; 42 | 43 | export default createReducer( {}, reducers ); 44 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/style.scss: -------------------------------------------------------------------------------- 1 | @import 'components/carrier-icon/style'; 2 | @import 'components/checkbox/style'; 3 | @import 'components/field-error/style'; 4 | @import 'components/info-tooltip/style'; 5 | @import 'components/settings-group-card/style'; 6 | @import 'components/text/style'; 7 | @import 'components/text-field/style'; 8 | @import 'views/label-settings/style'; 9 | @import 'views/carrier-accounts/style'; 10 | @import 'views/live-rates-carriers-list/style'; 11 | @import 'views/subscriptions-usage/style'; 12 | @import 'views/packages/style'; 13 | @import 'views/service-settings/settings-form/style'; 14 | @import 'views/service-settings/shipping-services/style'; 15 | @import 'views/shipping-label/style'; 16 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/carrier-accounts/dialog-styles.scss: -------------------------------------------------------------------------------- 1 | .carrier-accounts__settings-cancel-dialog { 2 | .dialog__content { 3 | padding: 0; 4 | max-width: 666px; 5 | min-height: 200px; 6 | 7 | .carrier-accounts__settings-cancel-dialog-header { 8 | background-color: #fcfcfc; 9 | border-bottom: 1px solid #ececec; 10 | display: flex; 11 | 12 | .carrier-accounts__settings-cancel-dialog-title { 13 | display: block; 14 | margin: auto 28px; 15 | } 16 | 17 | .carrier-accounts__settings-cancel-dialog-close-button { 18 | border: 0 none; 19 | outline: 0 none; 20 | background-color: inherit; 21 | height: 61px; 22 | width: 61px; 23 | border-left: 1px solid #ececec; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | margin-left: auto; 28 | margin-right: 0; 29 | cursor: pointer; 30 | } 31 | } 32 | .carrier-accounts__settings-cancel-dialog-description { 33 | margin: 28px; 34 | } 35 | 36 | } 37 | .dialog__action-buttons { 38 | .is-primary.is-scary { 39 | background-color: #eb0001; 40 | border-color: #ac120b; 41 | color: #fff; 42 | } 43 | .is-borderless { 44 | border: none; 45 | background: none; 46 | color: #636d75; 47 | padding-left: 0; 48 | padding-right: 0; 49 | 50 | &:hover { 51 | color: #3d4145; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/live-rates-carriers-list/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react' 5 | import { localize } from 'i18n-calypso' 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import ExtendedHeader from 'woocommerce/components/extended-header' 11 | import CarriersList from './carriers-list' 12 | 13 | const supportedCarrierIds = [ 'wc_services_usps', 'wc_services_dhlexpress' ]; 14 | 15 | const LiveRatesCarriersList = ( { translate, carrierIds } ) => { 16 | if ( carrierIds.some( ( carrierId ) => supportedCarrierIds.includes( carrierId ) ) === false ) { 17 | return null 18 | } 19 | 20 | return ( 21 |
22 | 26 | 27 |
28 | ) 29 | } 30 | 31 | export default localize( LiveRatesCarriersList ) 32 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/live-rates-carriers-list/style.scss: -------------------------------------------------------------------------------- 1 | .live-rates-carriers-list { 2 | .components-popover__content { 3 | width: 150px; 4 | } 5 | 6 | &__wrapper { 7 | padding: 0; 8 | } 9 | 10 | &__heading, 11 | &__element { 12 | display: flex; 13 | flex-direction: row; 14 | align-items: center; 15 | padding: 12px 24px; 16 | font-size: 14px; 17 | border-bottom: 1px solid #ccced0; 18 | flex-wrap: nowrap; 19 | 20 | > * { 21 | margin-right: 12px; 22 | 23 | &:last-child { 24 | margin-right: 0; 25 | } 26 | } 27 | 28 | &:last-child { 29 | border-bottom: none; 30 | } 31 | } 32 | 33 | &__heading { 34 | font-weight: 600; 35 | background: #f6f6f6; 36 | } 37 | 38 | &__element { 39 | padding: 24px 24px; 40 | } 41 | 42 | &__icon { 43 | width: 36px; 44 | } 45 | 46 | &__carrier { 47 | width: 35%; 48 | } 49 | 50 | &__features { 51 | width: 30%; 52 | flex-grow: 1; 53 | 54 | ul { 55 | margin-bottom: 0; 56 | margin-left: 1.5em; 57 | } 58 | } 59 | 60 | &__actions { 61 | display: flex; 62 | flex-direction: row; 63 | align-items: center; 64 | justify-content: flex-end; 65 | flex-wrap: nowrap; 66 | 67 | > * { 68 | margin-right: 12px; 69 | 70 | &:last-child { 71 | margin-right: 0; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/migrator-settings/style.scss: -------------------------------------------------------------------------------- 1 | .woocommerce-migrator-settings-root-view { 2 | margin-bottom: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/service-settings/settings-form/style.scss: -------------------------------------------------------------------------------- 1 | .settings-form__row { 2 | display: flex; 3 | 4 | & > * { 5 | flex-grow: 1; 6 | margin-right: 16px; 7 | 8 | &:last-child { 9 | margin-right: 0; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/label-item-in-progress.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { localize } from 'i18n-calypso'; 9 | 10 | /** 11 | * Internal dependencies 12 | */ 13 | import { LabelItem } from './label-item'; 14 | 15 | class LabelItemInProgress extends LabelItem { 16 | render() { 17 | const { label, translate } = this.props; 18 | const { serviceName, labelIndex } = label; 19 | 20 | return ( 21 |
22 |

23 | { translate( '%(service)s label (#%(labelIndex)d)', { 24 | args: { 25 | service: serviceName, 26 | labelIndex: labelIndex + 1, 27 | }, 28 | } ) } 29 |
30 | { translate( 'Purchasing…' ) } 31 |

32 |
33 | ); 34 | } 35 | } 36 | 37 | LabelItem.propTypes = { 38 | label: PropTypes.object.isRequired, 39 | }; 40 | 41 | export default localize( LabelItemInProgress ); 42 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/label-purchase-modal/packages-step/get-package-descriptions.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import { translate } from 'i18n-calypso'; 7 | import { mapValues } from 'lodash'; 8 | 9 | export default ( selected, all, addNames ) => { 10 | let pckgCount = 0; 11 | 12 | return mapValues( selected, pckg => { 13 | if ( 'individual' === pckg.box_id ) { 14 | return pckg.items[ 0 ].name; 15 | } 16 | 17 | pckgCount++; 18 | 19 | const pckgData = all[ pckg.box_id ]; 20 | const isEnvelope = pckgData && pckgData.is_letter; 21 | const pckgName = addNames && pckgData ? pckgData.name : false; 22 | 23 | if ( isEnvelope ) { 24 | return pckgName 25 | ? translate( 'Envelope %(packageNum)d: %(packageName)s', { 26 | args: { packageNum: pckgCount, packageName: pckgName }, 27 | } ) 28 | : translate( 'Envelope %(packageNum)d', { args: { packageNum: pckgCount } } ); 29 | } 30 | 31 | return pckgName 32 | ? translate( 'Package %(packageNum)d: %(packageName)s', { 33 | args: { packageNum: pckgCount, packageName: pckgName }, 34 | } ) 35 | : translate( 'Package %(packageNum)d', { args: { packageNum: pckgCount } } ); 36 | } ); 37 | }; 38 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/label-purchase-modal/purchase-section/style.scss: -------------------------------------------------------------------------------- 1 | .purchase-section { 2 | text-align: center; 3 | } 4 | .purchase-section__explanation { 5 | padding-top: 15px; 6 | font-size: 12px; 7 | color: gray; 8 | } 9 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/label-purchase-modal/step-confirmation-button/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | import FormButton from 'wcs-client/components/forms/form-button'; 13 | 14 | const StepConfirmationButton = ( { disabled, onClick, children } ) => { 15 | return ( 16 |
17 | 18 | { children } 19 | 20 |
21 | ); 22 | }; 23 | 24 | StepConfirmationButton.propTypes = { 25 | disabled: PropTypes.bool, 26 | onClick: PropTypes.func.isRequired, 27 | }; 28 | 29 | export default StepConfirmationButton; 30 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/label-purchase-modal/step-confirmation-button/style.scss: -------------------------------------------------------------------------------- 1 | .step-confirmation-button { 2 | padding: 16px 24px; 3 | margin: 0 -24px -24px; 4 | background: var( --color-neutral-0 ); 5 | border-top: 1px solid var( --color-neutral-0 ); 6 | 7 | .form-button { 8 | float: none; 9 | margin: 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/tracking-link.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { localize } from 'i18n-calypso'; 9 | import Gridicon from 'gridicons'; 10 | 11 | const TRACKING_URL_MAP = { 12 | usps: tracking => `https://tools.usps.com/go/TrackConfirmAction.action?tLabels=${ tracking }`, 13 | fedex: tracking => `https://www.fedex.com/apps/fedextrack/?action=track&tracknumbers=${ tracking }`, 14 | ups: tracking => `https://www.ups.com/track?loc=en_US&tracknum=${ tracking }`, 15 | dhlexpress: tracking => `https://www.dhl.com/en/express/tracking.html?AWB=${ tracking }&brand=DHL`, 16 | }; 17 | 18 | const TrackingLink = ( { tracking, carrierId, translate } ) => { 19 | if ( ! tracking ) { 20 | return { translate( 'N/A' ) }; 21 | } 22 | const url = TRACKING_URL_MAP[ carrierId ]( tracking ); 23 | if ( ! url ) { 24 | return { tracking }; 25 | } 26 | return ( 27 | 28 | { tracking } 29 | 30 | ); 31 | }; 32 | 33 | TrackingLink.propTypes = { 34 | tracking: PropTypes.string, 35 | carrierId: PropTypes.string, 36 | }; 37 | 38 | export default localize( TrackingLink ); 39 | -------------------------------------------------------------------------------- /client/extensions/woocommerce/woocommerce-services/views/shipping-label/tracking-modal/style.scss: -------------------------------------------------------------------------------- 1 | .tracking-modal__header { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .tracking-modal__close-button { 7 | align-self: flex-start; 8 | padding: 0; 9 | border: 0; 10 | margin-top: -7px; 11 | } 12 | 13 | .form-section-heading { 14 | flex: 1; 15 | } 16 | -------------------------------------------------------------------------------- /client/lib/calypso-boot/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import i18n from 'i18n-calypso'; 5 | 6 | export default function boot() { 7 | if ( ! window.i18nLocale ) { 8 | return; 9 | } 10 | 11 | const i18nLocaleStringsObject = JSON.parse( window.i18nLocale.json ); 12 | if ( ! i18nLocaleStringsObject || ! i18nLocaleStringsObject[ '' ] ) { 13 | return; 14 | } 15 | 16 | i18nLocaleStringsObject[ '' ].localeSlug = window.i18nLocale.localeSlug; 17 | i18n.setLocale( i18nLocaleStringsObject ); 18 | } 19 | 20 | boot(); 21 | -------------------------------------------------------------------------------- /client/lib/local-api-middleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import _ from 'lodash'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import { HTTP_REQUEST } from 'state/action-types'; 10 | 11 | const actionsToModify = []; 12 | 13 | /** 14 | * Look for Jetpack proxy requests that were localized as 15 | * indicated by the `meta.localApiRequest` flag. 16 | * 17 | * Modify their `dataLayer` meta to mimic the Jetpack proxy. 18 | * 19 | * @returns {Function} middleware function 20 | */ 21 | export default () => ( next ) => ( action ) => { 22 | if ( 23 | HTTP_REQUEST === action.type && 24 | _.get( action, 'meta.localApiRequest' ) && 25 | _.has( action, 'onSuccess.type' ) 26 | ) { 27 | // This request was a Jetpack proxy request that 28 | // was rewritten to hit the local WC API. 29 | actionsToModify.push( _.get( action, 'onSuccess.type' ) ); 30 | } else if ( 31 | -1 !== actionsToModify.indexOf( action.type ) && 32 | _.has( action, 'meta.dataLayer.data.body' ) 33 | ) { 34 | // The WooCommerce middleware expects this response from the 35 | // Jetpack REST API proxy, which nests the body under `data`. 36 | action.meta.dataLayer.data.data = action.meta.dataLayer.data.body; 37 | delete action.meta.dataLayer.data.body; 38 | 39 | // Don't process this action again. 40 | const actionIndex = actionsToModify.indexOf( action.type ); 41 | actionsToModify.splice( actionIndex, 1 ); 42 | } 43 | 44 | return next( action ); 45 | }; 46 | -------------------------------------------------------------------------------- /client/lib/utils/local-storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | //import from calypso 5 | import localStoragePolyfill from 'lib/local-storage-polyfill'; 6 | localStoragePolyfill(); 7 | 8 | export const MAX_AGE = 86400000; 9 | 10 | export const setWithExpiry = ( key, obj ) => { 11 | if ( ! obj ) { 12 | return; 13 | } 14 | 15 | const json = JSON.stringify( { 16 | ...obj, 17 | _timestamp: Date.now(), 18 | } ); 19 | localStorage.setItem( key, json ); 20 | }; 21 | 22 | export const getWithExpiry = ( key, maxAge = MAX_AGE ) => { 23 | const json = localStorage.getItem( key ); 24 | if ( json ) { 25 | const item = JSON.parse( json ); 26 | if ( maxAge > Date.now() - item._timestamp ) { 27 | delete item._timestamp; 28 | return item; 29 | } 30 | } 31 | 32 | return null; 33 | }; 34 | 35 | export const remove = ( key ) => { 36 | localStorage.removeItem( key ); 37 | }; 38 | -------------------------------------------------------------------------------- /client/lib/utils/parse-json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { translate as __ } from 'i18n-calypso'; 5 | 6 | /** 7 | * Parses a server response into a JSON object, providing a human-readable error if the 8 | * JSON is invalid. 9 | * 10 | * @param {Response} response Response object, as obtained from a fetch call. 11 | * @returns {Promise} Resolves with the parsed JSON object, 12 | * or rejects with a human-readable error if the payload is not valid JSON 13 | */ 14 | export default ( response ) => { 15 | if ( ! response instanceof Response ) { 16 | console.error( 'Invalid Response object' ); // eslint-disable-line no-console 17 | return Promise.reject( __( 'Unexpected server error.' ) ); 18 | } 19 | return response.text().then( ( text ) => { 20 | try { 21 | return JSON.parse( text ); 22 | } catch ( error ) { 23 | console.error( error ); // eslint-disable-line no-console 24 | console.error( text ); // eslint-disable-line no-console 25 | throw __( 'Unexpected server error.' ); 26 | } 27 | } ); 28 | }; 29 | -------------------------------------------------------------------------------- /client/lib/utils/sanitize-html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { sanitize } from 'dompurify'; 5 | 6 | export default ( html ) => { 7 | return { 8 | __html: sanitize( html, 9 | { 10 | ALLOWED_TAGS: [ 11 | 'a', 12 | 'strong', 13 | 'em', 14 | 'u', 15 | 'tt', 16 | 's', 17 | ], 18 | ALLOWED_ATTR: [ 19 | 'target', 20 | 'href', 21 | ], 22 | } 23 | ), 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /client/new-order-taxjar.js: -------------------------------------------------------------------------------- 1 | /*global woocommerce_admin_meta_boxes */ 2 | /** 3 | * External dependencies 4 | */ 5 | import jQuery from 'jquery'; 6 | 7 | jQuery( document ).ready( function() { 8 | /* 9 | * JavaScript for WooCommerce new order page 10 | */ 11 | const TaxJarOrder = function( $ ){ // eslint-disable-line no-unused-vars 12 | $( document ).ajaxSend( function( event, request, settings ) { 13 | // Making sure it's a recalculate event by checking `woocommerce_calc_line_taxes` before parsing to JSON. 14 | if ( settings.data && typeof settings.data === 'string' && settings.data.indexOf( 'woocommerce_calc_line_taxes' ) > 0 ) { 15 | const data = JSON.parse( '{"' + decodeURIComponent( settings.data.replace( /&/g, '","' ).replace( /=/g, '":"' ) ) + '"}' ); 16 | if ( 'woocommerce_calc_line_taxes' === data.action ) { 17 | let street = ''; 18 | if ( 'shipping' === woocommerce_admin_meta_boxes.tax_based_on ) { 19 | street = $( '#_shipping_address_1' ).val(); 20 | } 21 | if ( 'billing' === woocommerce_admin_meta_boxes.tax_based_on ) { 22 | street = $( '#_billing_address_1' ).val(); 23 | } 24 | data.street = street; 25 | settings.data = $.param( data ); 26 | } 27 | } 28 | } ); 29 | }( jQuery ); 30 | }); 31 | -------------------------------------------------------------------------------- /client/provide-public-path.js: -------------------------------------------------------------------------------- 1 | // Modify webpack pubilcPath at runtime based on location of WordPress Plugin. 2 | // eslint-disable-next-line no-undef 3 | __webpack_public_path__ = global.wcsPluginData.assetPath; 4 | -------------------------------------------------------------------------------- /default.env: -------------------------------------------------------------------------------- 1 | # WordPress 2 | WORDPRESS_DB_HOST=db:3306 3 | WORDPRESS_DB_USER=wordpress 4 | WORDPRESS_DB_PASSWORD=wordpress 5 | WORDPRESS_DB_NAME=wordpress 6 | WORDPRESS_DEBUG=1 7 | ABSPATH=/usr/src/wordpress/ 8 | 9 | # Database 10 | MYSQL_ROOT_PASSWORD=wordpress 11 | MYSQL_DATABASE=wordpress 12 | MYSQL_USER=wordpress 13 | MYSQL_PASSWORD=wordpress 14 | -------------------------------------------------------------------------------- /docker/mu-dev.php: -------------------------------------------------------------------------------- 1 | secret = 'askjdflasjdlfkajsldfkjaslkdf.asdkfjlaskjdflajskd'; 6 | $token->external_user_id = 0; 7 | 8 | return $token; 9 | } 10 | 11 | add_filter( 'wc_connect_jetpack_access_token', 'wc_connect_jetpack_dev_access_token' ); 12 | -------------------------------------------------------------------------------- /docker/mu-wccon-staging.php: -------------------------------------------------------------------------------- 1 | > $PHP_INI_DIR/php.ini \ 4 | && echo 'xdebug.remote_port=9000' >> $PHP_INI_DIR/php.ini \ 5 | && echo 'xdebug.remote_host=host.docker.internal' >> $PHP_INI_DIR/php.ini \ 6 | && echo 'xdebug.remote_autostart=0' >> $PHP_INI_DIR/php.ini \ 7 | && docker-php-ext-enable xdebug 8 | RUN apt-get update \ 9 | && apt-get install --assume-yes --quiet --no-install-recommends gnupg2 subversion mariadb-client less jq 10 | RUN apt-get install -y openssh-client 11 | RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ 12 | && chmod +x wp-cli.phar \ 13 | && mv wp-cli.phar /usr/local/bin/wp 14 | -------------------------------------------------------------------------------- /docker/wordpress_xdebug/Dockerfile-php81: -------------------------------------------------------------------------------- 1 | FROM wordpress:php8.1 2 | RUN pecl install xdebug \ 3 | && echo 'xdebug.remote_enable=1' >> $PHP_INI_DIR/php.ini \ 4 | && echo 'xdebug.remote_port=9000' >> $PHP_INI_DIR/php.ini \ 5 | && echo 'xdebug.remote_host=host.docker.internal' >> $PHP_INI_DIR/php.ini \ 6 | && echo 'xdebug.remote_autostart=0' >> $PHP_INI_DIR/php.ini \ 7 | && docker-php-ext-enable xdebug 8 | RUN apt-get update \ 9 | && apt-get install --assume-yes --quiet --no-install-recommends gnupg2 subversion mariadb-client less jq \ 10 | && apt-get update && apt-get install -y openssh-client \ 11 | libxml2-dev \ 12 | && docker-php-ext-install soap 13 | 14 | RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ 15 | && chmod +x wp-cli.phar \ 16 | && mv wp-cli.phar /usr/local/bin/wp 17 | -------------------------------------------------------------------------------- /images/payment-logos/ideal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/payment-logos/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/images/stripe.png -------------------------------------------------------------------------------- /images/wcs-notice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/images/wcs-notice.png -------------------------------------------------------------------------------- /images/wcshipping-migration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/images/wcshipping-migration.jpg -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.js': [ 3 | 'npm run eslint:fix', 4 | ], 5 | '*.php': [ 6 | 'sh bin/wc-phpcbf.sh', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/php 14 | 15 | 16 | -------------------------------------------------------------------------------- /tasks/i18n-download.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, import/no-nodejs-modules */ 2 | const fs = require('fs'); 3 | const request = require('request'); 4 | 5 | const download = function(uri, filename, callback){ 6 | request(uri).pipe(fs.createWriteStream(filename)).on('close', callback); 7 | }; 8 | 9 | const base_url = 'https://translate.wordpress.org/projects/wp-plugins/woocommerce-services/stable/'; 10 | const filename_prefix = __dirname + '/../i18n/languages/woocommerce-services-'; 11 | const supported_languages = { 12 | 'ar': 'ar', 13 | 'es-mx': 'es_MX', 14 | 'es-ve': 'es_VE', 15 | 'es': 'es_ES', 16 | 'fr-ca': 'fr_CA', 17 | 'ja': 'ja', 18 | 'nl': 'nl_NL', 19 | 'ru': 'ru', 20 | 'pt-br': 'pt_BR', 21 | 'ro': 'ro_RO', 22 | 'sv': 'sv_SE', 23 | 'zh-cn': 'zh_CN', 24 | }; 25 | 26 | for ( const locale in supported_languages ) { 27 | const fileLocale = supported_languages[ locale ]; 28 | for ( const format of [ 'po', 'mo' ] ) { 29 | download( base_url + locale + '/default/export-translations/?format=' + format, filename_prefix + fileLocale + '.' + format, () => { 30 | console.log('Downloaded ' + fileLocale + '.' + format); 31 | } ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tasks/i18n.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, import/no-nodejs-modules */ 2 | const i18nCli = require( 'i18n-calypso-cli' ); 3 | const glob = require('glob'); 4 | 5 | glob(__dirname + '/../dist/**/*.js', {}, (err, files) => { 6 | i18nCli( { 7 | inputPaths: files, 8 | output: 'i18n/strings.php', 9 | format: 'php', 10 | phpArrayName: 'i18nStrings', 11 | projectName: 'woocommerce-services', 12 | textdomain: 'woocommerce-services', 13 | } ); 14 | 15 | i18nCli( { 16 | inputPaths: files, 17 | output: 'i18n/languages/woocommerce-services.pot', 18 | format: 'pot', 19 | phpArrayName: 'i18nStrings', 20 | projectName: 'woocommerce-services', 21 | textdomain: 'woocommerce-services', 22 | } ); 23 | }) 24 | 25 | 26 | -------------------------------------------------------------------------------- /tasks/i18njson.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, import/no-nodejs-modules */ 2 | const fs = require( 'fs' ), 3 | globby = require( 'globby' ), 4 | path = require( 'path' ), 5 | po2json = require( 'po2json' ); 6 | 7 | const poFilePaths = globby.sync( [ 'i18n/languages/*.po' ] ); 8 | 9 | if ( ! poFilePaths.length ) { 10 | console.log( 'no .po files found' ); 11 | } 12 | 13 | poFilePaths.forEach( ( inputPath ) => { 14 | const file = fs.readFileSync( inputPath ); 15 | const json = po2json.parse( file, { stringify: true } ); 16 | 17 | const outputFilename = path.basename( inputPath, '.po' ) + '.json'; 18 | const outputPath = path.join( __dirname, '../i18n/languages', outputFilename ); 19 | 20 | fs.writeFile( outputPath, json, ( error ) => { 21 | if ( error ) { 22 | console.log( error ); 23 | } else { 24 | console.log( 'saved ' + outputFilename ); 25 | } 26 | } ); 27 | } ); 28 | -------------------------------------------------------------------------------- /tests/bin/e2e-test-integration.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn } = require( 'child_process' ); 4 | const program = require( 'commander' ); 5 | 6 | const baseUrl = process.env.WP_BASE_URL; 7 | const adminUserName = process.env.WP_ADMIN_USER_NAME; 8 | const adminUserPassword = process.env.WP_ADMIN_USER_PW; 9 | 10 | const envVariablesSet = baseUrl && adminUserName && adminUserPassword; 11 | 12 | if( ! envVariablesSet ) { 13 | ! baseUrl && console.error( "\033[31m WP_BASE_URL \x1b[0m environment variable is missing!"); 14 | ! adminUserName && console.error( "\033[31m WP_ADMIN_USER_NAME \x1b[0m environment variable is missing!"); 15 | ! adminUserPassword && console.error( "\033[31m WP_ADMIN_USER_PW \x1b[0m environment variable is missing!"); 16 | throw new Error( "Test configutartion variables missing!" ); 17 | } 18 | 19 | program 20 | .usage( ' [options]' ) 21 | .option( '--dev', 'Development mode' ) 22 | .parse( process.argv ); 23 | 24 | const testEnvVars = { 25 | NODE_ENV: 'test:e2e', 26 | JEST_PUPPETEER_CONFIG: 'tests/e2e-tests/config/jest-puppeteer.config.js', 27 | NODE_CONFIG_DIR: 'tests/e2e-tests/config', 28 | }; 29 | 30 | const envVars = Object.assign( {}, process.env, testEnvVars ); 31 | 32 | spawn( 33 | 'jest', 34 | [ 35 | '--runInBand', 36 | '--config=tests/e2e-tests/config/jest.config.js', 37 | '--rootDir=./', 38 | '--verbose', 39 | program.args, 40 | ], 41 | { 42 | stdio: 'inherit', 43 | env: envVars, 44 | } 45 | ); 46 | -------------------------------------------------------------------------------- /tests/bin/get-base-url.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, import/no-nodejs-modules */ 2 | const path = require( 'path' ); 3 | const fs = require( 'fs' ); 4 | 5 | const testConfigFilePath = path.resolve( __dirname, '../e2e/config/default.json' ); 6 | const testConfigFile = fs.readFileSync( testConfigFilePath ); 7 | const testConfig = JSON.parse( testConfigFile ); 8 | 9 | console.log( testConfig.url.slice( 0, -1 ) ); 10 | -------------------------------------------------------------------------------- /tests/bin/install-wc-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [wc-version]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | WC_VERSION=${6-"7.4.1"} 14 | 15 | export NVM_DIR="$HOME/.nvm" 16 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 17 | [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion 18 | 19 | install_wc() { 20 | git clone --depth=1 --branch=$WC_VERSION https://github.com/woocommerce/woocommerce.git /tmp/woocommerce && cd /tmp/woocommerce/plugins/woocommerce && nvm i && composer install && pnpm run build:feature-config && echo "Done unzipping WooCommerce" 21 | } 22 | 23 | install_wp() { 24 | echo "Installing WordPress via WooCommerce install script" && bash /tmp/woocommerce/plugins/woocommerce/tests/bin/install.sh $DB_NAME $DB_USER "$DB_PASS" $DB_HOST $WP_VERSION && echo "Done unzipping WordPress" 25 | } 26 | 27 | if [ -z "$TMPDIR" ]; then 28 | echo "Variable \$TMPDIR is not set. Unable to remove current wordpress-tests-lib directory if needed - this might cause problems." 29 | else 30 | echo "Removing directories wordpress and wordpress-tests-lib from \$TMPDIR ($TMPDIR)..." 31 | rm -rf $TMPDIR/wordpress $TMPDIR/wordpress-tests-lib 32 | fi 33 | 34 | rm -rf /tmp/woocommerce && install_wc && install_wp 35 | 36 | -------------------------------------------------------------------------------- /tests/bin/wait-for-e2e-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Max amount of time to wait for the Docker container to be built 3 | # Allowing 30 polling attempts, 10 seconds delay between each attempt 4 | MAX_ATTEMPTS=30 5 | 6 | # Delay (in seconds) between each polling attempt 7 | DELAY_SEC=10 8 | 9 | # Counter for the loop that checks if the Docker container had been built 10 | count=0 11 | WP_BASE_URL=$(node ./tests/bin/get-base-url.js) 12 | printf "Testing URL: $WP_BASE_URL\n\n" 13 | ready_page=$(curl -s ${WP_BASE_URL}/?pagename=ready) 14 | 15 | while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${WP_BASE_URL}/?pagename=ready)" != "200" || $ready_page != *"E2E-tests"* ]] 16 | do 17 | echo "$(date) - Docker container is still being built" 18 | sleep ${DELAY_SEC} 19 | 20 | ((count++)) 21 | ready_page=$(curl -s ${WP_BASE_URL}/?pagename=ready) 22 | 23 | if [[ $count -gt ${MAX_ATTEMPTS} ]]; then 24 | echo "$(date) - Docker container couldn't be built" 25 | exit 1 26 | fi 27 | done 28 | 29 | if [[ $count -ge 0 ]]; then 30 | echo "$(date) - Docker container had been built successfully" 31 | fi 32 | -------------------------------------------------------------------------------- /tests/bin/wc_rest_api_credentials.php: -------------------------------------------------------------------------------- 1 | 1, 9 | 'permissions' => $permissions, 10 | 'consumer_key' => wc_api_hash( $consumer_key ), 11 | 'consumer_secret' => $consumer_secret, 12 | 'truncated_key' => substr( $consumer_key, -7 ), 13 | ); 14 | 15 | global $wpdb; 16 | $wpdb->insert( 17 | $wpdb->prefix . 'woocommerce_api_keys', 18 | $data, 19 | array( 20 | '%d', 21 | '%s', 22 | '%s', 23 | '%s', 24 | '%s', 25 | '%s', 26 | ) 27 | ); 28 | -------------------------------------------------------------------------------- /tests/client/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | module.exports = { 4 | moduleNameMapper: { 5 | '^config$': '/wp-calypso/server/config/index.js', 6 | "^wcs-client/(.*)$": "/client/$1" 7 | }, 8 | transform: { 9 | '^.+\\.jsx?$': '/tests/test/helpers/assets/babel-transform.js', 10 | '\\.(gif|jpg|jpeg|png|svg|scss|sass|css)$': '/tests/test/helpers/assets/transform.js', 11 | }, 12 | modulePaths: [ 13 | '/tests/', 14 | '/client/', 15 | '/client/extensions/', 16 | '/wp-calypso/client', 17 | '/wp-calypso/node_modules' 18 | ], 19 | rootDir: './../../', 20 | roots: [ '/client/' ], 21 | testEnvironment: 'jsdom', 22 | transformIgnorePatterns: [ 23 | 'node_modules[\\/\\\\](?!flag-icon-css|redux-form|simple-html-tokenizer|draft-js)', 24 | ], 25 | testMatch: [ '/client/**/test/*.js?(x)', '!**/.eslintrc.*' ], 26 | testURL: 'https://example.com', 27 | setupFiles: [ 'regenerator-runtime/runtime' ], // some NPM-published packages depend on the global 28 | setupFilesAfterEnv: [ '/tests/client/setup-test-framework.js' ], 29 | verbose: false, 30 | globals: { 31 | google: {}, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /tests/e2e/config/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | const { useE2EJestPuppeteerConfig } = require( '@woocommerce/e2e-environment' ); 2 | 3 | module.exports = useE2EJestPuppeteerConfig( { 4 | launch: { 5 | slowMo: 100, 6 | args: [ '--window-size=1920,1080', '--user-agent=chrome --runInBand --maxWorkers=4' ], 7 | } 8 | } ); 9 | -------------------------------------------------------------------------------- /tests/e2e/config/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow strict 3 | * @format 4 | */ 5 | const path = require( 'path' ); 6 | const { useE2EJestConfig } = require( '@woocommerce/e2e-environment' ); 7 | 8 | // https://jestjs.io/docs/en/configuration.html 9 | 10 | module.exports = useE2EJestConfig( { 11 | // Automatically clear mock calls and instances between every test 12 | clearMocks: true, 13 | 14 | // An array of file extensions your modules use 15 | moduleFileExtensions: [ 'js' ], 16 | 17 | preset: 'jest-puppeteer', 18 | 19 | // Where to look for test files 20 | roots: [ path.resolve( __dirname, '../specs' ) ], 21 | 22 | // The glob patterns Jest uses to detect test files 23 | testMatch: [ '**/*.(test|spec).js' ], 24 | } ); 25 | -------------------------------------------------------------------------------- /tests/e2e/config/jest.setup.js: -------------------------------------------------------------------------------- 1 | /** format */ 2 | 3 | //Set the default test timeout to 120s 4 | const jestTimeoutInMilliSeconds = 120000; 5 | 6 | jest.setTimeout( jestTimeoutInMilliSeconds ); 7 | -------------------------------------------------------------------------------- /tests/e2e/config/travis/travis_default-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | listen [::]:8080 ipv6only=on; 4 | 5 | root /home/travis/wordpress; 6 | 7 | access_log /tmp/access.log; 8 | error_log /tmp/error.log; 9 | 10 | index index.php; 11 | 12 | location ~* "\.php(/|$)" { 13 | include fastcgi.conf; 14 | fastcgi_pass php; 15 | } 16 | 17 | location / { 18 | # This is cool because no php is touched for static content. 19 | # include the "?$args" part so non-default permalinks doesn't break when using query string 20 | try_files $uri $uri/ /index.php?$args; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/e2e/config/travis/travis_nginx.conf: -------------------------------------------------------------------------------- 1 | error_log /tmp/error.log; 2 | pid /tmp/nginx.pid; 3 | worker_processes 1; 4 | 5 | events { 6 | worker_connections 1024; 7 | } 8 | 9 | http { 10 | # Set an array of temp and cache file options that will otherwise default to restricted locations accessible only to root. 11 | client_body_temp_path /tmp/client_body; 12 | fastcgi_temp_path /tmp/fastcgi_temp; 13 | proxy_temp_path /tmp/proxy_temp; 14 | scgi_temp_path /tmp/scgi_temp; 15 | uwsgi_temp_path /tmp/uwsgi_temp; 16 | 17 | ## 18 | # Basic Settings 19 | ## 20 | sendfile on; 21 | tcp_nopush on; 22 | tcp_nodelay on; 23 | keepalive_timeout 65; 24 | types_hash_max_size 2048; 25 | # server_tokens off; 26 | # server_names_hash_bucket_size 64; 27 | # server_name_in_redirect off; 28 | include /etc/nginx/mime.types; 29 | default_type application/octet-stream; 30 | 31 | ## 32 | # Logging Settings 33 | ## 34 | access_log /tmp/access.log; 35 | error_log /tmp/error.log; 36 | 37 | ## 38 | # Gzip Settings 39 | ## 40 | gzip on; 41 | gzip_disable "msie6"; 42 | 43 | ## 44 | # Virtual Host Configs 45 | ## 46 | # include /tmp/nginx/conf.d/*.conf; 47 | include /home/travis/nginx/sites-enabled/*; 48 | 49 | upstream php { 50 | server 127.0.0.1:9000; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/e2e/config/travis/travis_php-fpm.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | 3 | [travis] 4 | user = $USER 5 | listen = 9000 6 | listen.mode = 0666 7 | pm = static 8 | pm.max_children = 5 9 | php_admin_value[memory_limit] = 256M 10 | -------------------------------------------------------------------------------- /tests/e2e/config/travis/wc-services-testing-helper.php: -------------------------------------------------------------------------------- 1 | 'admin', 17 | 'email' => 'admin@e2ewootestsite.com', 18 | ); 19 | set_transient( 'jetpack_connected_user_data_1', $master_user_data ); 20 | 21 | function wc_test_connect_jetpack_access_fake_token( $token ) { 22 | $token = new stdClass(); 23 | $token->secret = 'askjdflasjdlfkajsldfkjaslkdf.asdkfjlaskjdflajskd'; 24 | $token->external_user_id = 0; 25 | return $token; 26 | } 27 | 28 | add_filter( 'wc_connect_jetpack_access_token', 'wc_test_connect_jetpack_access_fake_token' ); 29 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/create-shipping-label.json: -------------------------------------------------------------------------------- 1 | { 2 | "async": true, 3 | "origin": { 4 | "company": "WCShip Test", 5 | "name": "admin", 6 | "phone": "", 7 | "country": "US", 8 | "state": "OH", 9 | "address": "928 College Avenue", 10 | "address_2": "", 11 | "city": "Dayton", 12 | "postcode": "45402" 13 | }, 14 | "destination": { 15 | "address_2": "", 16 | "city": "Corinth", 17 | "state": "NY", 18 | "postcode": "12822", 19 | "country": "US", 20 | "address": "4590 Oak Drive", 21 | "name": "John Doe", 22 | "company": "", 23 | "phone": "4121111111" 24 | }, 25 | "packages": [ 26 | { 27 | "id": "default_box", 28 | "box_id": "priority_4", 29 | "length": 7, 30 | "width": 7, 31 | "height": 6, 32 | "weight": 20, 33 | "is_letter": false, 34 | "shipment_id": null, 35 | "rate_id": null, 36 | "service_id": "Priority", 37 | "carrier_id": "usps", 38 | "service_name": "USPS - Priority Mail", 39 | "products": [ 40 | ] 41 | } 42 | ], 43 | "email_receipt": true 44 | } 45 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/orders.json: -------------------------------------------------------------------------------- 1 | { 2 | "payment_method": "bacs", 3 | "payment_method_title": "Direct Bank Transfer", 4 | "set_paid": true, 5 | "billing": { 6 | "first_name": "John", 7 | "last_name": "Doe", 8 | "address_1": "4590 Oak Drive", 9 | "address_2": "", 10 | "city": "Corinth", 11 | "state": "NY", 12 | "postcode": "12822", 13 | "country": "US", 14 | "email": "john.doe@example.com", 15 | "phone": "(555) 555-5555" 16 | }, 17 | "shipping": { 18 | "first_name": "John", 19 | "last_name": "Doe", 20 | "address_1": "4590 Oak Drive", 21 | "address_2": "", 22 | "city": "Corinth", 23 | "state": "NY", 24 | "postcode": "12822", 25 | "country": "US" 26 | }, 27 | "line_items": [ 28 | ], 29 | "shipping_lines": [ 30 | { 31 | "method_id": "flat_rate", 32 | "method_title": "Flat Rate", 33 | "total": "10" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Simple Product Test", 3 | "type": "simple", 4 | "regular_price": "21.99", 5 | "description": "This is a fantastic product." 6 | } 7 | -------------------------------------------------------------------------------- /tests/e2e/specs/admin/1-activate-extension.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { StoreOwnerFlow } from '../../utils'; 5 | 6 | describe( 'Store admin can login and make sure WooCommerce Tax extension is activated', () => { 7 | it( 'Can activate WooCommerce Tax extension if it is deactivated' , async () => { 8 | const slug = 'woocommerce-services' 9 | await StoreOwnerFlow.login(); 10 | await StoreOwnerFlow.updateWPDB(); 11 | await StoreOwnerFlow.openSettings(); // Navigate to a WC settings page first for issue #2729 temporary solution. 12 | await StoreOwnerFlow.openPluginsPage(); 13 | 14 | await expect( page.title() ).resolves.toContain( 'Plugins' ); 15 | const disableLink = await page.$( `a#deactivate-${ slug }` ); 16 | 17 | if ( disableLink ) { 18 | return; 19 | } 20 | 21 | await page.waitForSelector( `a#activate-${ slug }` ); 22 | 23 | await page.click( `a#activate-${ slug }` ); 24 | 25 | await page.waitForSelector( `tr[data-slug="${ slug }"] .deactivate a` ); 26 | }); 27 | } ); 28 | -------------------------------------------------------------------------------- /tests/e2e/utils/components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | /** 6 | * Internal dependencies 7 | */ 8 | import { StoreOwnerFlow } from './flows'; 9 | import { waitForSelectorAndText } from '.'; 10 | 11 | export const deleteAllPackages = async () => { 12 | await StoreOwnerFlow.login(); 13 | await StoreOwnerFlow.openSettings( 'shipping', 'woocommerce-services-settings' ); 14 | 15 | // Wait for "Add package" to finish loading, click "edit" once this session is loaded 16 | await waitForSelectorAndText('.button:not([disabled])', 'Add package'); 17 | 18 | let editButton = await page.$( '.button.is-compact', { text: 'Edit' } ); 19 | while ( editButton ) { 20 | await expect( page ).toClick( '.button.is-compact', { text: 'Edit' } ); 21 | // Delete package 22 | await page.waitForSelector( '.packages__add-edit-title.form-section-heading', { text: 'Edit package' } ); 23 | await expect( page ).toClick( '.button.packages__delete.is-scary.is-borderless', { text: 'Delete this package' } ); 24 | editButton = await page.$( '.button.is-compact', { text: 'Edit' } ); 25 | } 26 | }; 27 | 28 | // Click save and wait until it's saved. 29 | export const saveAndWait = async () => { 30 | await expect( page ).toClick( '.button.is-primary', { text: 'Save changes' } ); 31 | await page.waitForSelector( '.button.is-primary.is-busy', { hidden: true } ); 32 | }; 33 | -------------------------------------------------------------------------------- /tests/test/helpers/assets/README.md: -------------------------------------------------------------------------------- 1 | # Mocks asset imports in JS 2 | 3 | Jest would fail when trying to import asset files that it cannot parse, 4 | i.e. they're not plain JavaScript: 5 | 6 | ```jsx 7 | const MyFlag = ( { countryCode } ) => ( 8 | 12 | ); 13 | 14 | ``` 15 | 16 | This helper can be used to transform all those imports to a module that returns 17 | the asset file's basename as a string: 18 | 19 | ```json 20 | { 21 | "transform": { 22 | "^.+\\.jsx?$": "babel-jest", 23 | "\\.svg$": "/test/test/helpers/assets/transform.js" 24 | }, 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /tests/test/helpers/assets/babel-transform.js: -------------------------------------------------------------------------------- 1 | // A super simple jest transformer so we can set a custom path for babel.config.js. 2 | 3 | const { createTransformer } = require( 'babel-jest' ); 4 | const { map } = require( 'lodash' ); 5 | const config = require( '../../../../wp-calypso/babel.config' ); 6 | 7 | // Remap a couple paths for new root location. 8 | config.plugins = map( config.plugins, ( plugin ) => { 9 | return './inline-imports.js' === plugin ? './wp-calypso/inline-imports.js' : plugin; 10 | } ); 11 | config.env.test.plugins = map( config.env.test.plugins, ( plugin ) => { 12 | return './server/bundler/babel/babel-lodash-es' === plugin ? './wp-calypso/server/bundler/babel/babel-lodash-es' : plugin; 13 | } ); 14 | 15 | module.exports = createTransformer( config ); 16 | -------------------------------------------------------------------------------- /tests/test/helpers/assets/transform.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | const path = require( 'path' ); 7 | 8 | module.exports = { 9 | process( src, filename ) { 10 | return 'module.exports = ' + JSON.stringify( path.basename( filename ) ) + ';'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /tests/test/helpers/config/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | /** 3 | * Internal dependencies 4 | */ 5 | import config from 'config'; 6 | 7 | export const setFeatureFlag = ( feature, val ) => { 8 | const c = config; 9 | let spy; 10 | 11 | beforeAll( () => { 12 | spy = jest 13 | .spyOn( config, 'isEnabled' ) 14 | .mockImplementation( feat => ( feat === feature ? val : c.isEnabled( feat ) ) ); 15 | } ); 16 | 17 | afterAll( () => { 18 | spy.mockReset(); 19 | spy.mockRestore(); 20 | } ); 21 | }; 22 | -------------------------------------------------------------------------------- /tests/test/helpers/console/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export const captureConsole = testFn => ( callback = () => {} ) => { 4 | const original = console; 5 | const replacement = { 6 | log: jest.fn(), 7 | warn: jest.fn(), 8 | error: jest.fn(), 9 | }; 10 | console = replacement; 11 | let val; 12 | try { 13 | val = testFn(); 14 | } finally { 15 | console = original; 16 | } 17 | callback( replacement ); 18 | return val; 19 | }; 20 | -------------------------------------------------------------------------------- /tests/test/helpers/use-nock/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | /** 3 | * External dependencies 4 | */ 5 | import debug from 'debug'; 6 | import nock from 'nock'; 7 | import { partial } from 'lodash'; 8 | 9 | export { nock }; 10 | 11 | const log = debug( 'calypso:test:use-nock' ); 12 | 13 | /** 14 | * @param {Function} setupCallback Function executed before all tests are run. 15 | * @deprecated Use nock directly instead. 16 | */ 17 | export const useNock = setupCallback => { 18 | if ( setupCallback ) { 19 | beforeAll( partial( setupCallback, nock ) ); 20 | } 21 | afterAll( () => { 22 | log( 'Cleaning up nock' ); 23 | nock.cleanAll(); 24 | } ); 25 | }; 26 | 27 | export default useNock; 28 | -------------------------------------------------------------------------------- /tests/test/helpers/use-nock/integration/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | /** 4 | * External dependencies 5 | */ 6 | 7 | import { expect } from 'chai'; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | import { nock, useNock } from '../index.js'; 13 | 14 | describe( 'useNock', () => { 15 | useNock(); 16 | 17 | describe( 'Messy without useNock', () => { 18 | test( 'sets up a persistent interceptor', () => { 19 | nock( 'wordpress.com' ) 20 | .persist() 21 | .get( '/me' ) 22 | .reply( 200, { id: 42 } ); 23 | } ); 24 | } ); 25 | 26 | describe( 'Illustration Block', () => { 27 | test( 'still sees the earlier persistent connection', () => { 28 | expect( nock.isDone() ).to.be.false; 29 | } ); 30 | 31 | afterAll( () => nock.cleanAll() ); 32 | } ); 33 | 34 | describe( 'Clean with useNock', () => { 35 | useNock(); 36 | 37 | test( 'sets up a persistent interceptor', () => { 38 | nock( 'wordpress.com' ) 39 | .persist() 40 | .get( '/me' ) 41 | .reply( 200, { id: 42 } ); 42 | 43 | expect( nock.isDone() ).to.be.false; 44 | } ); 45 | 46 | test( 'persists inside the same `describe` block', () => { 47 | expect( nock.isDone() ).to.be.false; 48 | } ); 49 | } ); 50 | 51 | describe( 'Test Block', () => { 52 | test( 'should have reset all remaining nocks', () => { 53 | expect( nock.isDone() ).to.be.true; 54 | } ); 55 | } ); 56 | } ); 57 | -------------------------------------------------------------------------------- /tests/test/helpers/use-sinon/README.md: -------------------------------------------------------------------------------- 1 | # Sinon Test Helpers 2 | A set of helpers for folks using sinon to fake, mock, spy and bend time. 3 | 4 | ## Usage 5 | 6 | ### Full sandbox 7 | ```js 8 | import { useSandbox } from 'test/helpers/use-sinon'; 9 | 10 | describe( 'my tests that use a sandbox and arrow functions', function() { 11 | let sandbox = null; 12 | 13 | useSandbox( newSandbox => { 14 | sandbox = newSandbox; 15 | }); 16 | 17 | it( 'should have a full suite of sandbox things available', () => { 18 | // this will get torn down automatically by the sandbox 19 | const onWindowClick = sandbox.stub( global.window, 'onclick' ); 20 | } ); 21 | } ); 22 | ``` 23 | 24 | ### Fake Clock 25 | ```js 26 | import { useFakeTimers } from 'test/helpers/use-sinon'; 27 | 28 | describe( 'my time dependent test', function() { 29 | let clock; 30 | const aLongTimeAgo = Date.parse( '1976-09-15T010:00:00Z' ).valueOf(), 31 | yearInMillis = 1000 * 60 * 60 * 24 * 365; 32 | 33 | useFakeTimers( aLongTimeAgo, newClock => { 34 | clock = newClock; 35 | } ); 36 | 37 | it( 'can access the fake clock via this', function() { 38 | assert.equals( Date.now(), aLongTimeAgo ); 39 | clock.tick( yearInMillis ); 40 | assert.equals( Date.now(), aLongTimeAgo + yearInMillis ); 41 | } ); 42 | } ); 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /wordpress_org_assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/banner-1544x500.png -------------------------------------------------------------------------------- /wordpress_org_assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/banner-772x250.png -------------------------------------------------------------------------------- /wordpress_org_assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/icon-128x128.png -------------------------------------------------------------------------------- /wordpress_org_assets/icon-160x160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/icon-160x160.png -------------------------------------------------------------------------------- /wordpress_org_assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/icon-256x256.png -------------------------------------------------------------------------------- /wordpress_org_assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/screenshot-1.png -------------------------------------------------------------------------------- /wordpress_org_assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/woocommerce-services/44664a97585ad5fd4263db53aa9e4a3e143da8db/wordpress_org_assets/screenshot-2.png --------------------------------------------------------------------------------