Commit d9aecfae640 for woocommerce

commit d9aecfae64096e46eda1e56f8de9b67c8efc0fc2
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Tue Aug 4 18:12:33 2026 +0300

    [Payments NOX] Add Airwallex and KOMOJU to payment method recommendations in Japan (#67359)

    * feat: Add Airwallex to payment providers recommendations in Japan

    Airwallex was missing from the Payments NOX recommendations for Japan
    despite being tracked as an available payment partner. This adds it
    to the "other payment providers" suggestions for Japan, placed before
    Visa so Visa remains the final card in that group.

    Refs WOOPRD-3640

    * feat: Add KOMOJU to payment providers recommendations in Japan

    Eligible merchants in Japan could not discover KOMOJU through
    WooCommerce payment providers recommendations. This registers KOMOJU
    as an official payment partner for Japan, using its WordPress.org
    plugin slug (komoju-japanese-payments) and WooCommerce Marketplace
    listing. Its provider metadata also includes KOMOJU pricing and terms
    of service links, plus WooCommerce documentation and support links.

    KOMOJU is placed in the "Other payment providers" group after Square
    and before Airwallex, without the `preferred` tag.

    Refs WOOPRD-3639

    * fix: Point the KOMOJU gateway's Manage link to its dedicated settings tab

    The KOMOJU extension registers its legacy combined gateway (id
    `komoju`) alongside a dedicated "KOMOJU" settings tab where merchants
    actually connect their account and pick payment methods; that tab is
    where the plugin's own admin notice tells merchants to go. Without a
    custom provider, WooCommerce core's Payments settings page fell back
    to the generic `admin.php?page=wc-settings&tab=checkout&section=komoju`
    URL for the Manage button, which does not lead anywhere useful.

    Add a Komoju payments provider class, following the same pattern as
    Mollie's, that overrides get_settings_url() to point to
    `admin.php?page=wc-settings&tab=komoju_settings` instead, and register
    it for the `komoju` gateway ID only — the per-method `komoju_*`
    gateways created after method selection keep their own real settings
    sections.

    Refs WOOPRD-3639

    * fix: Detect KOMOJU's sandbox mode and account-connection status

    The generic PaymentGateway provider heuristics for test mode and
    account-connection status don't match how the KOMOJU extension
    represents either: it derives sandbox mode from the `sk_test_`/
    `sk_live_` prefix of the stored secret key (no dedicated toggle,
    method, or option the generic heuristics recognize), and it has no
    "is connected" API at all, so account_connected always fell back to
    the base class's default of `true` regardless of whether a key was
    ever saved.

    Add is_in_test_mode() and is_account_connected() overrides to the
    Komoju provider class, following the same defensive shape used by
    Stripe (class_exists()/is_callable() guard, try/catch with debug
    logging, fallback to the parent behavior) and Mollie (direct,
    try/catch-guarded reads of the extension's own WordPress options
    when it exposes no public API) respectively. is_account_connected()
    mirrors the plugin's own get_option_compat() precedence: the current
    global secret key option first, then the legacy per-gateway settings
    array.

    Verified against the real installed extension: with no secret key
    saved, the Payments providers endpoint now correctly reports
    account_connected=false (previously always true); with an sk_test_
    key saved, it reports test_mode=true and account_connected=true.

    Refs WOOPRD-3639

    * fix: Fix KOMOJU test-mode detection and extend it to per-method gateways

    Komoju::is_in_test_mode() depended entirely on the KOMOJU extension's
    own komoju_is_test_mode() static method, which only exists starting
    with extension v3.2.9. On older installs the is_callable() guard
    silently failed and test mode was reported as false even with an
    sk_test_ key saved. Separately, the account-connection and test-mode
    overrides were registered only for the legacy 'komoju' gateway ID, not
    the per-method komoju_* gateways WooCommerce creates once a merchant
    connects and selects payment methods — so those gateway rows, the ones
    merchants actually enable at checkout, kept falling through to the
    generic PaymentGateway heuristics instead.

    Read the secret key directly and derive test mode from its sk_test_/
    sk_live_ prefix locally instead of delegating to the extension's
    method, removing the version dependency entirely. Map 'komoju_*' to
    the same Komoju provider class as 'komoju', matching the established
    pattern already used by Stripe, Mollie, Paystack, Monei, and Paymob
    for their own exact-ID/wildcard-ID pairs, and branch
    get_settings_url() by gateway ID the same way Mollie's provider
    already does for its own gateway variants — the legacy gateway still
    redirects to the KOMOJU settings tab, while per-method gateways defer
    to their own settings section instead.

    Refs WOOPRD-3639

    * test: Remove redundant option cleanup from KomojuTest

    Same class of dead code fixed in class-wc-admin-permalink-settings-test.php:
    komoju_woocommerce_secret_key and woocommerce_komoju_settings are never
    written by WooCommerce core itself, only read by Komoju::is_in_test_mode()
    and is_account_connected(). WC_Unit_Test_Case wraps every test in a DB
    transaction and flushes the object cache in its own tearDown(), so any
    option a test sets via update_option() already reverts to its pre-test
    baseline (absent, in this case) automatically.

    Verified with a throwaway probe test confirming both options are absent
    before any test runs and don't survive a test that sets them, with zero
    manual cleanup. KomojuTest still passes in both fixed and random order
    after removing the setUp()/tearDown() delete_option() calls.

    * test: Remove redundant currency restore from PaymentsProvidersTest

    Same class of dead code as KomojuTest and class-wc-admin-permalink-settings-test.php:
    WC_Unit_Test_Case wraps every test in a DB transaction and flushes the
    object cache in its own tearDown(), so any option a test changes via
    update_option()/delete_option() — including woocommerce_currency, which
    some tests here mutate directly — already reverts to its pre-test baseline
    automatically.

    Verified with a throwaway probe test: after deleting and reassigning
    woocommerce_currency twice with zero manual cleanup, the following test
    still observed the original baseline value. PaymentsProvidersTest still
    passes in both fixed and random order after removing the manual capture
    and restore.

    * fix: Report KOMOJU sandbox credentials as a test account

    The Komoju provider inferred the sandbox environment from the `sk_test_`
    secret key prefix, but wired that signal only into `is_in_test_mode()`.
    `is_in_test_mode_onboarding()` was left to the base implementation, which
    probes the gateway object for `is_test_mode_onboarding()` /
    `is_in_test_mode_onboarding()` and returns false when neither exists. The
    KOMOJU extension defines neither, so `onboarding.state.test_mode` read
    false for every store, sandbox or not.

    That misreported two consumers: the list item rendered the "Test mode"
    badge instead of "Test account", and the `provider_account_test_mode`
    analytics field recorded false on sandbox stores. A `sk_test_` key is a
    sandbox credential, so the account itself is a test account rather than a
    live account processing test payments.

    Extract the prefix check into `is_komoju_in_sandbox_mode(): ?bool` and
    feed both methods from it, letting a null bubble up to the parent when no
    key is saved. This matches how every other single-signal provider is
    wired (Mollie, PayPal, Airwallex, Antom, Eway, and others).

    Refs WOOPRD-3639

    * fix: Add the missing from attribution arg to custom settings links

    `PaymentGateway::get_settings_url()` appends `from=WCADMIN_PAYMENT_SETTINGS`
    to every settings link it returns, so the destination screen can tell that
    the merchant arrived from the Payments settings page. Providers that build
    a custom URL and return it before reaching the parent bypassed that.

    Two did: the KOMOJU legacy combined gateway, which points at KOMOJU's own
    settings tab, and Mollie, whose links go through `get_custom_settings_url()`.
    Their manage links were the only ones on the Payments page without the arg,
    losing the attribution breadcrumb. The KOMOJU per-method gateways were
    unaffected since they defer to the parent.

    Append the arg in both, so every link path in these classes carries it.

    Refs WOOPRD-3639

    * refactor: Prefer KOMOJU's own helper for sandbox detection

    Sandbox detection reproduced the extension's secret key prefix check
    rather than calling it. The two agree today - `komoju_is_test_mode()`
    reads the same two options, applies the same legacy fallback, and tests
    the same `sk_test_` prefix - but a copy drifts if KOMOJU ever stops
    inferring the environment from the key.

    Call the helper when it is available and keep the local check for when it
    is not. It only landed in extension version 3.2.9, so every earlier
    release still needs the reproduction, which rules out calling it
    unconditionally.

    Read the saved key before either branch. The helper reports a store with
    no key at all as live, whereas an unconfigured store should stay
    undetermined so the caller can fall back to the generic detection.

    Refs WOOPRD-3639

    * refactor: Read KOMOJU's secret key off the gateway instance

    Sandbox detection and the connected-account check resolved the secret key
    by reading `komoju_woocommerce_secret_key` and the legacy
    `woocommerce_komoju_settings` array directly. Both option names were
    hardcoded here, so renaming either upstream would silently report every
    store as disconnected and live.

    The gateway already resolves the key for itself: `$gateway->secretKey` is
    set in the constructor from `get_option_compat()`, which reads the same
    global option and applies the same legacy fallback. Read that instead, and
    keep the direct reads for extension versions older than 2.5.0, which
    predate the property.

    Note that the gateway's own `get_option()` cannot serve this. It reads
    `woocommerce_{$id}_settings`, which for the per-method `komoju_*` gateways
    is not where the shared key lives, so it would report each of them as
    disconnected.

    Refs WOOPRD-3639

    * fix: Fall back when KOMOJU's gateway resolves an empty secret key

    Reading the key off the gateway instance guarded on `isset()`, which is
    true for an empty string. Extension versions older than 2.5.0 populate
    `$secretKey` through `WC_Settings_API::get_option()`, which yields `''`
    rather than null when nothing is stored, so those versions returned an
    empty key and never consulted the options underneath.

    On such a version the store then reported as disconnected and live even
    with a key saved in `komoju_woocommerce_secret_key`, because only the
    legacy settings array feeds the property before 2.5.0. Reading the key off
    the instance was meant to preserve behaviour, and here it did not.

    Guard on non-empty instead, so an empty resolved key falls through to the
    option reads. A store with no key anywhere still reports disconnected,
    since the fallback comes back empty too.

    Found by running the branch against extension version 2.0.0 locally.

    Refs WOOPRD-3639

diff --git a/plugins/woocommerce/assets/images/onboarding/icons/komoju.svg b/plugins/woocommerce/assets/images/onboarding/icons/komoju.svg
new file mode 100644
index 00000000000..519c9f72f12
--- /dev/null
+++ b/plugins/woocommerce/assets/images/onboarding/icons/komoju.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40"><rect width="40" height="40" fill="#fff"/><g transform="translate(10.674 9.725) scale(.6598)"><path fill="#3cc239" d="M19.4738.63623H28.2754L16.4149 15.5939H7.61328L19.4738.63623Z"/><path fill="#3cc239" d="M19.4738 30.542H28.2754L16.4149 15.5939H7.61328L19.4738 30.542Z"/><path fill="#172e44" d="M7.34051.637939H0V30.5437H7.34051V.637939Z"/></g></svg>
diff --git a/plugins/woocommerce/changelog/wooprd-3639-new-payment-partner-komoju b/plugins/woocommerce/changelog/wooprd-3639-new-payment-partner-komoju
new file mode 100644
index 00000000000..f8add502e9b
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooprd-3639-new-payment-partner-komoju
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add KOMOJU to payment providers recommendations in Japan.
diff --git a/plugins/woocommerce/changelog/wooprd-3640-new-payment-partner-airwallex-jp b/plugins/woocommerce/changelog/wooprd-3640-new-payment-partner-airwallex-jp
new file mode 100644
index 00000000000..458e82f371c
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooprd-3640-new-payment-partner-airwallex-jp
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add Airwallex to payment providers recommendations in Japan.
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
index b916110e3ea..29591e97d68 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders.php
@@ -15,6 +15,7 @@ use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Helcim;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\HelioPay;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Klarna;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\KlarnaCheckout;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Komoju;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\MercadoPago;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Mollie;
 use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Monei;
@@ -115,6 +116,9 @@ class PaymentsProviders {
 		'stripe_*'                    => Stripe::class,
 		'mollie'                      => Mollie::class,
 		'mollie_wc_gateway_*'         => Mollie::class, // Target all the Mollie gateways.
+		'komoju'                      => Komoju::class,
+		// Target all the per-method KOMOJU gateways.
+		'komoju_*'                    => Komoju::class,
 		'amazon_payments_advanced*'   => AmazonPay::class,
 		'woo-mercado-pago-*'          => MercadoPago::class,
 		'affirm'                      => Affirm::class,
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Komoju.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Komoju.php
new file mode 100644
index 00000000000..721b9df2dc8
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Komoju.php
@@ -0,0 +1,176 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders;
+
+use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
+use Automattic\WooCommerce\Internal\Logging\SafeGlobalFunctionProxy;
+use Throwable;
+use WC_Payment_Gateway;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * KOMOJU payment gateway provider class.
+ *
+ * This class handles all the custom logic for the KOMOJU payment gateway provider.
+ */
+class Komoju extends PaymentGateway {
+
+	/**
+	 * Get the settings URL for a payment gateway.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return string The settings URL for the payment gateway.
+	 */
+	public function get_settings_url( WC_Payment_Gateway $payment_gateway ): string {
+		// The legacy combined gateway has no settings section of its own; account connection
+		// and payment method selection happen on KOMOJU's dedicated settings tab instead.
+		// Per-method gateways (`komoju_*`) already have a real settings section, so defer to
+		// the generic gateway settings URL logic for those.
+		if ( 'komoju' !== $payment_gateway->id ) {
+			return parent::get_settings_url( $payment_gateway );
+		}
+
+		return add_query_arg(
+			array(
+				'from' => Payments::FROM_PAYMENTS_SETTINGS,
+			),
+			admin_url( 'admin.php?page=wc-settings&tab=komoju_settings' )
+		);
+	}
+
+	/**
+	 * Try to determine if the payment gateway is in test mode.
+	 *
+	 * This is a best-effort attempt, as there is no standard way to determine this.
+	 * Trust the true value, but don't consider a false value as definitive.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return bool True if the payment gateway is in test mode, false otherwise.
+	 */
+	public function is_in_test_mode( WC_Payment_Gateway $payment_gateway ): bool {
+		return $this->is_komoju_in_sandbox_mode( $payment_gateway ) ?? parent::is_in_test_mode( $payment_gateway );
+	}
+
+	/**
+	 * Try to determine if the payment gateway is in test mode onboarding (aka sandbox).
+	 *
+	 * This is a best-effort attempt, as there is no standard way to determine this.
+	 * Trust the true value, but don't consider a false value as definitive.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return bool True if the payment gateway is in test mode onboarding, false otherwise.
+	 */
+	public function is_in_test_mode_onboarding( WC_Payment_Gateway $payment_gateway ): bool {
+		// A `sk_test_` key is a sandbox credential, so the account itself is a test account,
+		// not just a live account processing test payments. KOMOJU exposes no separate
+		// onboarding environment signal, so the same check answers both questions.
+		return $this->is_komoju_in_sandbox_mode( $payment_gateway ) ?? parent::is_in_test_mode_onboarding( $payment_gateway );
+	}
+
+	/**
+	 * Check if the KOMOJU payment gateway is in sandbox mode.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return ?bool True if the payment gateway is in sandbox mode, false otherwise.
+	 *               Null if the environment could not be determined.
+	 */
+	private function is_komoju_in_sandbox_mode( WC_Payment_Gateway $payment_gateway ): ?bool {
+		try {
+			// Check for a saved key first. The extension's helper below reports a store with no
+			// key at all as live, but we want that case to stay undetermined so the caller decides.
+			$secret_key = $this->get_secret_key( $payment_gateway );
+			if ( empty( $secret_key ) ) {
+				return null;
+			}
+
+			// Prefer the extension's own helper so we keep tracking how KOMOJU determines its
+			// environment, should that ever stop being a secret key check.
+			if ( class_exists( 'WC_Gateway_Komoju' ) && is_callable( 'WC_Gateway_Komoju::komoju_is_test_mode' ) ) {
+				return wc_string_to_bool( \WC_Gateway_Komoju::komoju_is_test_mode() );
+			}
+
+			// The helper only exists since extension version 3.2.9, so reproduce it for older
+			// versions: KOMOJU has no dedicated test-mode setting and infers the environment from
+			// whether the stored secret key has the `sk_test_` (vs. `sk_live_`) prefix.
+			return str_starts_with( $secret_key, 'sk_test_' );
+		} catch ( Throwable $e ) {
+			// Do nothing but log so we can investigate.
+			SafeGlobalFunctionProxy::wc_get_logger()->debug(
+				'Failed to determine if gateway is in sandbox mode: ' . $e->getMessage(),
+				array(
+					'gateway'   => $payment_gateway->id,
+					'source'    => 'settings-payments',
+					'exception' => $e,
+				)
+			);
+		}
+
+		// Let the caller know that we couldn't determine the environment.
+		return null;
+	}
+
+	/**
+	 * Check if the payment gateway has a payments processor account connected.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return bool True if the payment gateway account is connected, false otherwise.
+	 *              If the payment gateway does not provide the information, it will return true.
+	 */
+	public function is_account_connected( WC_Payment_Gateway $payment_gateway ): bool {
+		try {
+			// KOMOJU doesn't expose a dedicated "is connected" API. It considers the merchant
+			// connected once a secret key is saved.
+			return ! empty( $this->get_secret_key( $payment_gateway ) );
+		} catch ( Throwable $e ) {
+			// Do nothing but log so we can investigate.
+			SafeGlobalFunctionProxy::wc_get_logger()->debug(
+				'Failed to determine if gateway account is connected: ' . $e->getMessage(),
+				array(
+					'gateway'   => $payment_gateway->id,
+					'source'    => 'settings-payments',
+					'exception' => $e,
+				)
+			);
+		}
+
+		return parent::is_account_connected( $payment_gateway );
+	}
+
+	/**
+	 * Get the KOMOJU secret key.
+	 *
+	 * @param WC_Payment_Gateway $payment_gateway The payment gateway object.
+	 *
+	 * @return string The secret key, or an empty string if none is saved.
+	 */
+	private function get_secret_key( WC_Payment_Gateway $payment_gateway ): string {
+		// Prefer the key the gateway resolved for itself, so we keep tracking where KOMOJU
+		// sources it from. Its `get_option_compat()` reads the current global option and falls
+		// back to the legacy per-gateway settings array, which is what we reproduce below.
+		// Guard on non-empty rather than isset(): versions older than 2.5.0 populate the property
+		// through `WC_Settings_API::get_option()`, which yields an empty string rather than null
+		// when nothing is stored, and that must still fall through to the reads below.
+		if ( ! empty( $payment_gateway->secretKey ) && is_string( $payment_gateway->secretKey ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+			return $payment_gateway->secretKey; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+		}
+
+		// The property only exists since extension version 2.5.0, so reproduce its resolution
+		// for older versions. Note that we cannot use the gateway's own `get_option()` here:
+		// it reads `woocommerce_{$id}_settings`, which for the per-method `komoju_*` gateways
+		// is not where the shared key lives.
+		$secret_key = get_option( 'komoju_woocommerce_secret_key' );
+		if ( empty( $secret_key ) ) {
+			$legacy_settings = get_option( 'woocommerce_komoju_settings' );
+			$secret_key      = is_array( $legacy_settings ) ? ( $legacy_settings['secretKey'] ?? '' ) : '';
+		}
+
+		return is_string( $secret_key ) ? $secret_key : '';
+	}
+}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Mollie.php b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Mollie.php
index f1f99add827..592fdcb32cb 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Mollie.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/PaymentsProviders/Mollie.php
@@ -3,6 +3,7 @@ declare( strict_types=1 );

 namespace Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders;

+use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
 use Automattic\WooCommerce\Internal\Logging\SafeGlobalFunctionProxy;
 use Throwable;
 use WC_Payment_Gateway;
@@ -154,7 +155,12 @@ class Mollie extends PaymentGateway {
 			$settings_url = add_query_arg( 'section', $section, $settings_url );
 		}

-		return $settings_url;
+		return add_query_arg(
+			array(
+				'from' => Payments::FROM_PAYMENTS_SETTINGS,
+			),
+			$settings_url
+		);
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php b/plugins/woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php
index 584f2255178..4a8442f3e86 100644
--- a/plugins/woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php
+++ b/plugins/woocommerce/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php
@@ -46,6 +46,7 @@ class PaymentsExtensionSuggestions {
 	const KLARNA            = 'klarna';
 	const KLARNA_CHECKOUT   = 'klarna_checkout';
 	const HELCIM            = 'helcim';
+	const KOMOJU            = 'komoju';
 	const HELIOPAY          = 'heliopay';
 	const MONEI             = 'monei';
 	const COINBASE          = 'coinbase';
@@ -2020,6 +2021,8 @@ class PaymentsExtensionSuggestions {
 					),
 				),
 			),
+			self::KOMOJU,
+			self::AIRWALLEX,
 			self::VISA,
 			self::PAYPAL_WALLET,
 			self::AMAZON_PAY,
@@ -4167,6 +4170,38 @@ class PaymentsExtensionSuggestions {
 					),
 				),
 			),
+			self::KOMOJU            => array(
+				'_type'       => self::TYPE_PSP,
+				'title'       => esc_html__( 'KOMOJU Payments', 'woocommerce' ),
+				'description' => esc_html__( 'Easily add popular Japanese payment methods like konbini, PayPay, and more with KOMOJU’s secure extension to optimize checkout.', 'woocommerce' ),
+				'icon'        => plugins_url( 'assets/images/onboarding/icons/komoju.svg', WC_PLUGIN_FILE ),
+				'plugin'      => array(
+					'_type' => self::PLUGIN_TYPE_WPORG,
+					'slug'  => 'komoju-japanese-payments',
+				),
+				'links'       => array(
+					array(
+						'_type' => PaymentsProviders::LINK_TYPE_PRICING,
+						'url'   => 'https://en.komoju.com/pricing/',
+					),
+					array(
+						'_type' => PaymentsProviders::LINK_TYPE_ABOUT,
+						'url'   => 'https://woocommerce.com/products/komoju-japanese-payments/',
+					),
+					array(
+						'_type' => PaymentsProviders::LINK_TYPE_TERMS,
+						'url'   => 'https://toc.komoju.com/toc/',
+					),
+					array(
+						'_type' => PaymentsProviders::LINK_TYPE_DOCS,
+						'url'   => 'https://woocommerce.com/document/komoju-japanese-payments/',
+					),
+					array(
+						'_type' => PaymentsProviders::LINK_TYPE_SUPPORT,
+						'url'   => 'https://woocommerce.com/my-account/contact-support/?select=komoju-japanese-payments',
+					),
+				),
+			),
 			self::HELIOPAY          => array(
 				'_type'       => self::TYPE_CRYPTO,
 				'title'       => esc_html__( 'Helio Pay', 'woocommerce' ),
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/FakeKomojuPaymentGateway.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/FakeKomojuPaymentGateway.php
new file mode 100644
index 00000000000..a698a508da5
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/Mocks/FakeKomojuPaymentGateway.php
@@ -0,0 +1,23 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks;
+
+/**
+ * Fake KOMOJU payment gateway to use in tests.
+ *
+ * Mirrors the public `$secretKey` property that the KOMOJU extension resolves in its
+ * gateway constructor, so tests can cover the provider path that reads it.
+ */
+class FakeKomojuPaymentGateway extends FakePaymentGateway {
+
+	/**
+	 * The secret key the gateway resolved for itself.
+	 *
+	 * Left null to stand in for extension versions older than 2.5.0, which have no such
+	 * property. The provider guards with isset(), so a null here exercises its fallback.
+	 *
+	 * @var ?string
+	 */
+	public $secretKey; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/KomojuTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/KomojuTest.php
new file mode 100644
index 00000000000..08425d91ae3
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/KomojuTest.php
@@ -0,0 +1,322 @@
+<?php
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\PaymentsProviders;
+
+use Automattic\WooCommerce\Internal\Admin\Settings\Payments;
+use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\Komoju;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
+use Automattic\WooCommerce\Testing\Tools\TestingContainer;
+use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakeKomojuPaymentGateway;
+use Automattic\WooCommerce\Tests\Internal\Admin\Settings\Mocks\FakePaymentGateway;
+use PHPUnit\Framework\MockObject\MockObject;
+use WC_Unit_Test_Case;
+
+/**
+ * KOMOJU payment gateway provider service test.
+ *
+ * @class Komoju
+ */
+class KomojuTest extends WC_Unit_Test_Case {
+
+	/**
+	 * @var MockableLegacyProxy|MockObject
+	 */
+	protected $mockable_proxy;
+
+	/**
+	 * @var Komoju
+	 */
+	protected $sut;
+
+	/**
+	 * Set up test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		/**
+		 * TestingContainer instance.
+		 *
+		 * @var TestingContainer $container
+		 */
+		$container = wc_get_container();
+
+		$this->mockable_proxy = $container->get( LegacyProxy::class );
+
+		$this->sut = new Komoju( $this->mockable_proxy );
+	}
+
+	/**
+	 * Get a fake KOMOJU gateway object for testing.
+	 *
+	 * @param array $extra_props Optional. Additional gateway properties to apply on top of the defaults.
+	 *
+	 * @return FakePaymentGateway
+	 */
+	private function get_fake_gateway( array $extra_props = array() ): FakePaymentGateway {
+		return new FakePaymentGateway(
+			'komoju',
+			array_merge(
+				array(
+					'enabled'            => true,
+					'plugin_slug'        => 'komoju-japanese-payments',
+					'plugin_file'        => 'komoju-japanese-payments/index',
+					'method_title'       => 'KOMOJU',
+					'method_description' => 'Deprecated legacy combined gateway.',
+				),
+				$extra_props
+			),
+		);
+	}
+
+	/**
+	 * Test that the settings URL points to KOMOJU's own dedicated settings tab,
+	 * not the generic per-gateway settings section.
+	 */
+	public function test_get_settings_url_points_to_komoju_settings_tab(): void {
+		// Act.
+		$settings_url = $this->sut->get_settings_url( $this->get_fake_gateway() );
+
+		// Assert.
+		$this->assertSame(
+			add_query_arg(
+				array( 'from' => Payments::FROM_PAYMENTS_SETTINGS ),
+				admin_url( 'admin.php?page=wc-settings&tab=komoju_settings' )
+			),
+			$settings_url
+		);
+	}
+
+	/**
+	 * @testdox get_settings_url() defers to the gateway's own settings section for per-method
+	 *          gateways, since they already have a real settings section unlike the legacy gateway.
+	 */
+	public function test_get_settings_url_defers_to_the_gateways_own_settings_section_for_a_per_method_gateway(): void {
+		$gateway = new FakePaymentGateway(
+			'komoju_konbini',
+			array(
+				'method_title' => 'KOMOJU - Konbini',
+				'settings_url' => 'https://example.com/wp-admin/admin.php?page=wc-settings&tab=checkout&section=komoju_konbini',
+			)
+		);
+
+		$settings_url = $this->sut->get_settings_url( $gateway );
+
+		$this->assertSame(
+			add_query_arg( array( 'from' => Payments::FROM_PAYMENTS_SETTINGS ), $gateway->get_settings_url() ),
+			$settings_url
+		);
+	}
+
+	/**
+	 * @testdox is_in_test_mode() returns true when the current global secret key has the `sk_test_` prefix.
+	 */
+	public function test_is_in_test_mode_returns_true_with_a_test_secret_key(): void {
+		update_option( 'komoju_woocommerce_secret_key', 'sk_test_123' );
+
+		$is_test_mode = $this->sut->is_in_test_mode( $this->get_fake_gateway() );
+
+		$this->assertTrue( $is_test_mode );
+	}
+
+	/**
+	 * @testdox is_in_test_mode() returns false when the current global secret key has the `sk_live_` prefix.
+	 */
+	public function test_is_in_test_mode_returns_false_with_a_live_secret_key(): void {
+		update_option( 'komoju_woocommerce_secret_key', 'sk_live_456' );
+
+		$is_test_mode = $this->sut->is_in_test_mode( $this->get_fake_gateway() );
+
+		$this->assertFalse( $is_test_mode );
+	}
+
+	/**
+	 * @testdox is_in_test_mode() returns true when only the legacy per-gateway secret key
+	 *          is set with the `sk_test_` prefix.
+	 */
+	public function test_is_in_test_mode_returns_true_with_the_legacy_secret_key(): void {
+		update_option( 'woocommerce_komoju_settings', array( 'secretKey' => 'sk_test_789' ) );
+
+		$is_test_mode = $this->sut->is_in_test_mode( $this->get_fake_gateway() );
+
+		$this->assertTrue( $is_test_mode );
+	}
+
+	/**
+	 * @testdox is_in_test_mode() falls back to the generic detection when no secret key is saved anywhere,
+	 *          instead of erroring out.
+	 */
+	public function test_is_in_test_mode_falls_back_when_no_secret_key_is_saved(): void {
+		// The fake gateway's own is_test_mode() returns true, proving this genuinely
+		// delegates to the generic provider fallback rather than defaulting to false.
+		$is_test_mode = $this->sut->is_in_test_mode( $this->get_fake_gateway( array( 'test_mode' => true ) ) );
+
+		$this->assertTrue( $is_test_mode );
+	}
+
+	/**
+	 * @testdox is_in_test_mode_onboarding() returns true with a test secret key, since a `sk_test_`
+	 *          credential means the account itself is a sandbox account, not just a live account
+	 *          processing test payments.
+	 */
+	public function test_is_in_test_mode_onboarding_returns_true_with_a_test_secret_key(): void {
+		update_option( 'komoju_woocommerce_secret_key', 'sk_test_123' );
+
+		$is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $this->get_fake_gateway() );
+
+		$this->assertTrue( $is_test_mode_onboarding );
+	}
+
+	/**
+	 * @testdox is_in_test_mode_onboarding() returns false when the secret key has the `sk_live_` prefix.
+	 */
+	public function test_is_in_test_mode_onboarding_returns_false_with_a_live_secret_key(): void {
+		update_option( 'komoju_woocommerce_secret_key', 'sk_live_456' );
+
+		$is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $this->get_fake_gateway() );
+
+		$this->assertFalse( $is_test_mode_onboarding );
+	}
+
+	/**
+	 * @testdox is_in_test_mode_onboarding() returns true when only the legacy per-gateway secret key
+	 *          is set with the `sk_test_` prefix.
+	 */
+	public function test_is_in_test_mode_onboarding_returns_true_with_the_legacy_secret_key(): void {
+		update_option( 'woocommerce_komoju_settings', array( 'secretKey' => 'sk_test_789' ) );
+
+		$is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding( $this->get_fake_gateway() );
+
+		$this->assertTrue( $is_test_mode_onboarding );
+	}
+
+	/**
+	 * @testdox is_in_test_mode_onboarding() falls back to the generic detection when no secret key
+	 *          is saved anywhere, instead of erroring out.
+	 */
+	public function test_is_in_test_mode_onboarding_falls_back_when_no_secret_key_is_saved(): void {
+		// The fake gateway's own is_in_test_mode_onboarding() returns true, proving this genuinely
+		// delegates to the generic provider fallback rather than defaulting to false.
+		$is_test_mode_onboarding = $this->sut->is_in_test_mode_onboarding(
+			$this->get_fake_gateway( array( 'test_mode_onboarding' => true ) )
+		);
+
+		$this->assertTrue( $is_test_mode_onboarding );
+	}
+
+	/**
+	 * @testdox is_in_test_mode() reads the key the gateway resolved for itself, in preference to
+	 *          the options we would otherwise read ourselves.
+	 */
+	public function test_is_in_test_mode_prefers_the_key_resolved_by_the_gateway(): void {
+		// Arrange. The option says live, the gateway says test - the gateway must win.
+		update_option( 'komoju_woocommerce_secret_key', 'sk_live_456' );
+		$gateway = new FakeKomojuPaymentGateway( 'komoju' );
+		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+		$gateway->secretKey = 'sk_test_123';
+
+		// Act.
+		$is_test_mode = $this->sut->is_in_test_mode( $gateway );
+
+		// Assert.
+		$this->assertTrue( $is_test_mode );
+	}
+
+	/**
+	 * @testdox is_account_connected() returns true from the key the gateway resolved for itself,
+	 *          even when nothing is saved in the options we read as a fallback.
+	 */
+	public function test_is_account_connected_uses_the_key_resolved_by_the_gateway(): void {
+		// Arrange. No options saved, so only the gateway's own key can answer this.
+		$gateway = new FakeKomojuPaymentGateway( 'komoju' );
+		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+		$gateway->secretKey = 'sk_live_456';
+
+		// Act.
+		$is_connected = $this->sut->is_account_connected( $gateway );
+
+		// Assert.
+		$this->assertTrue( $is_connected );
+	}
+
+	/**
+	 * @testdox The secret key falls back to the options when the gateway has no resolved key,
+	 *          matching extension versions older than 2.5.0.
+	 */
+	public function test_secret_key_falls_back_to_the_options_for_older_extension_versions(): void {
+		// Arrange. The gateway leaves its key null, the way pre-2.5.0 versions do.
+		update_option( 'komoju_woocommerce_secret_key', 'sk_test_123' );
+		$gateway = new FakeKomojuPaymentGateway( 'komoju' );
+
+		// Act.
+		$is_test_mode = $this->sut->is_in_test_mode( $gateway );
+		$is_connected = $this->sut->is_account_connected( $gateway );
+
+		// Assert.
+		$this->assertTrue( $is_test_mode );
+		$this->assertTrue( $is_connected );
+	}
+
+	/**
+	 * @testdox An empty string on the gateway falls through to the options, since extension
+	 *          versions older than 2.5.0 populate the property via `WC_Settings_API::get_option()`,
+	 *          which yields '' rather than null when nothing is stored.
+	 */
+	public function test_secret_key_falls_back_when_the_gateway_resolved_an_empty_key(): void {
+		// Arrange. Only the global option holds a key; the gateway resolved an empty string.
+		update_option( 'komoju_woocommerce_secret_key', 'sk_test_global111' );
+		$gateway = new FakeKomojuPaymentGateway( 'komoju' );
+		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+		$gateway->secretKey = '';
+
+		// Act.
+		$is_test_mode = $this->sut->is_in_test_mode( $gateway );
+		$is_connected = $this->sut->is_account_connected( $gateway );
+
+		// Assert.
+		$this->assertTrue( $is_test_mode );
+		$this->assertTrue( $is_connected );
+	}
+
+	/**
+	 * @testdox is_account_connected() returns false when no secret key is saved anywhere.
+	 */
+	public function test_is_account_connected_returns_false_without_a_secret_key(): void {
+		// Act.
+		$is_connected = $this->sut->is_account_connected( $this->get_fake_gateway() );
+
+		// Assert.
+		$this->assertFalse( $is_connected );
+	}
+
+	/**
+	 * @testdox is_account_connected() returns true when the current global secret key option is set.
+	 */
+	public function test_is_account_connected_returns_true_with_the_global_secret_key(): void {
+		// Arrange.
+		update_option( 'komoju_woocommerce_secret_key', 'sk_test_123' );
+
+		// Act.
+		$is_connected = $this->sut->is_account_connected( $this->get_fake_gateway() );
+
+		// Assert.
+		$this->assertTrue( $is_connected );
+	}
+
+	/**
+	 * @testdox is_account_connected() falls back to the legacy per-gateway settings array
+	 *          when the global secret key option is not set, matching the plugin's own compat logic.
+	 */
+	public function test_is_account_connected_returns_true_with_the_legacy_secret_key(): void {
+		// Arrange.
+		update_option( 'woocommerce_komoju_settings', array( 'secretKey' => 'sk_live_456' ) );
+
+		// Act.
+		$is_connected = $this->sut->is_account_connected( $this->get_fake_gateway() );
+
+		// Assert.
+		$this->assertTrue( $is_connected );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
index 4c4f1767d63..05d27571588 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProvidersTest.php
@@ -40,13 +40,6 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
 	 */
 	protected $store_admin_id;

-	/**
-	 * The previous store currency value to restore in tearDown.
-	 *
-	 * @var string|null
-	 */
-	private $prev_currency;
-
 	/**
 	 * Set up test.
 	 */
@@ -56,9 +49,6 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
 		$this->store_admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
 		wp_set_current_user( $this->store_admin_id );

-		// Save the current currency to restore in tearDown.
-		$this->prev_currency = get_option( 'woocommerce_currency', null );
-
 		$this->mock_extension_suggestions = $this->getMockBuilder( ExtensionSuggestions::class )
 			->disableOriginalConstructor()
 			->getMock();
@@ -82,11 +72,6 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
 			$this->sut->clear_cache();
 		}

-		// Restore the previous currency to prevent test leakage.
-		if ( null !== $this->prev_currency ) {
-			update_option( 'woocommerce_currency', $this->prev_currency );
-		}
-
 		parent::tearDown();
 	}

@@ -449,6 +434,25 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * Test getting payment gateway provider instance returns the KOMOJU provider for a per-method wildcard match.
+	 */
+	public function test_get_payment_gateway_provider_instance_returns_komoju_provider_for_wildcard() {
+		// Arrange - komoju_* pattern matches komoju_konbini, and should return the Komoju provider,
+		// same as the exact 'komoju' gateway ID.
+		$gateway_id = 'komoju_konbini';
+
+		// Act.
+		$provider = $this->sut->get_payment_gateway_provider_instance( $gateway_id );
+
+		// Assert.
+		$this->assertInstanceOf(
+			PaymentsProviders\Komoju::class,
+			$provider,
+			'Should return Komoju provider for wildcard match'
+		);
+	}
+
 	/**
 	 * Test getting payment gateway provider instance returns generic provider when no mapping exists.
 	 */
@@ -865,7 +869,13 @@ class PaymentsProvidersTest extends WC_Unit_Test_Case {
 		// Assert that the custom provider supplied details are returned.
 		$this->assertSame( 'mollie_wc_gateway_bogus', $gateway_details['id'] );
 		// This settings URL is provided by the custom provider.
-		$this->assertSame( admin_url( 'admin.php?page=wc-settings&tab=mollie_settings&section=mollie_payment_methods' ), $gateway_details['management']['_links']['settings']['href'] );
+		$this->assertSame(
+			add_query_arg(
+				array( 'from' => Payments::FROM_PAYMENTS_SETTINGS ),
+				admin_url( 'admin.php?page=wc-settings&tab=mollie_settings&section=mollie_payment_methods' )
+			),
+			$gateway_details['management']['_links']['settings']['href']
+		);
 		$this->assertTrue( $gateway_details['state']['test_mode'] ); // It should be in test mode because of the DB options. The custom provider logic handles this.

 		// Clean up.
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestionsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestionsTest.php
index 48b1bfcff4f..baf5ecfd6d0 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestionsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Suggestions/PaymentsExtensionSuggestionsTest.php
@@ -218,7 +218,7 @@ class PaymentsExtensionSuggestionsTest extends WC_Unit_Test_Case {
 			'HK' => 8,
 			'IN' => 7,
 			'ID' => 4,
-			'JP' => 7,
+			'JP' => 9,
 			'MY' => 5,
 			'NC' => 3,
 			'NZ' => 9,
@@ -611,7 +611,7 @@ class PaymentsExtensionSuggestionsTest extends WC_Unit_Test_Case {
 			'HK' => 8,
 			'IN' => 7,
 			'ID' => 4,
-			'JP' => 7,
+			'JP' => 9,
 			'MY' => 5,
 			'NC' => 3,
 			'NZ' => 9,
@@ -1063,6 +1063,66 @@ class PaymentsExtensionSuggestionsTest extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * @testdox Visa is the last "other payment provider" in Japan.
+	 */
+	public function test_visa_is_last_other_payment_provider_in_jp(): void {
+		$extensions = $this->sut->get_country_extensions( 'JP' );
+		$psp_ids    = array_column(
+			array_filter(
+				$extensions,
+				static fn( array $extension ): bool => PaymentsExtensionSuggestions::TYPE_PSP === $extension['_type']
+			),
+			'id'
+		);
+
+		$this->assertContains( PaymentsExtensionSuggestions::AIRWALLEX, $psp_ids, 'Airwallex should be suggested in JP.' );
+		$this->assertSame(
+			PaymentsExtensionSuggestions::VISA,
+			end( $psp_ids ),
+			'Visa should be the last PSP suggestion in JP.'
+		);
+	}
+
+	/**
+	 * @testdox KOMOJU is suggested between Square and Airwallex in Japan.
+	 */
+	public function test_komoju_is_suggested_between_square_and_airwallex_in_jp(): void {
+		$extensions       = $this->sut->get_country_extensions( 'JP' );
+		$extensions_by_id = array_column( $extensions, null, 'id' );
+		$psp_ids          = array_column(
+			array_filter(
+				$extensions,
+				static fn( array $extension ): bool => PaymentsExtensionSuggestions::TYPE_PSP === $extension['_type']
+			),
+			'id'
+		);
+		$komoju           = $extensions_by_id[ PaymentsExtensionSuggestions::KOMOJU ] ?? null;
+
+		$this->assertContains( PaymentsExtensionSuggestions::KOMOJU, $psp_ids, 'KOMOJU should be suggested in JP.' );
+		$this->assertSame(
+			array_search( PaymentsExtensionSuggestions::SQUARE, $psp_ids, true ) + 1,
+			array_search( PaymentsExtensionSuggestions::KOMOJU, $psp_ids, true ),
+			'KOMOJU should immediately follow Square in JP.'
+		);
+		$this->assertSame(
+			array_search( PaymentsExtensionSuggestions::KOMOJU, $psp_ids, true ) + 1,
+			array_search( PaymentsExtensionSuggestions::AIRWALLEX, $psp_ids, true ),
+			'KOMOJU should immediately precede Airwallex in JP.'
+		);
+
+		$this->assertIsArray( $komoju, 'KOMOJU should be suggested in JP.' );
+		if ( ! is_array( $komoju ) ) {
+			return;
+		}
+
+		$this->assertNotContains(
+			PaymentsExtensionSuggestions::TAG_PREFERRED,
+			$komoju['tags'],
+			'KOMOJU should remain in other payment options for JP.'
+		);
+	}
+
 	/**
 	 * Data provider for Helcim's supported countries.
 	 *
@@ -1124,6 +1184,55 @@ class PaymentsExtensionSuggestionsTest extends WC_Unit_Test_Case {
 		$this->assertNotEmpty( $extension['description'] );
 	}

+	/**
+	 * @testdox KOMOJU has complete base suggestion details.
+	 */
+	public function test_komoju_has_complete_base_details(): void {
+		$extension = $this->sut->get_by_id( 'komoju' );
+
+		$this->assertIsArray( $extension );
+		if ( ! is_array( $extension ) ) {
+			return;
+		}
+
+		$this->assertSame( PaymentsExtensionSuggestions::TYPE_PSP, $extension['_type'] );
+		$this->assertSame(
+			array(
+				'_type' => PaymentsExtensionSuggestions::PLUGIN_TYPE_WPORG,
+				'slug'  => 'komoju-japanese-payments',
+			),
+			$extension['plugin']
+		);
+		$this->assertEqualsCanonicalizing(
+			array(
+				array(
+					'_type' => PaymentsProviders::LINK_TYPE_PRICING,
+					'url'   => 'https://en.komoju.com/pricing/',
+				),
+				array(
+					'_type' => PaymentsProviders::LINK_TYPE_ABOUT,
+					'url'   => 'https://woocommerce.com/products/komoju-japanese-payments/',
+				),
+				array(
+					'_type' => PaymentsProviders::LINK_TYPE_TERMS,
+					'url'   => 'https://toc.komoju.com/toc/',
+				),
+				array(
+					'_type' => PaymentsProviders::LINK_TYPE_DOCS,
+					'url'   => 'https://woocommerce.com/document/komoju-japanese-payments/',
+				),
+				array(
+					'_type' => PaymentsProviders::LINK_TYPE_SUPPORT,
+					'url'   => 'https://woocommerce.com/my-account/contact-support/?select=komoju-japanese-payments',
+				),
+			),
+			$extension['links']
+		);
+		$this->assertNotEmpty( $extension['icon'] );
+		$this->assertNotEmpty( $extension['title'] );
+		$this->assertNotEmpty( $extension['description'] );
+	}
+
 	/**
 	 * Test getting payment extension suggestions by ID.
 	 */