# U2Code Product Multicurrency — Developer Guide

The plugin is built to be extended. Everything below is a stable public
contract; anything not listed here may change without notice.

## PHP API

```php
use U2Code\Multicurrency\API;

API::getCurrentCurrencyCode();          // 'EUR'
API::getCurrentCurrency();              // Currency value object (or null before boot)
API::getEnabledCurrencies();            // [ 'USD' => Currency, 'EUR' => Currency ]
API::convert( 10.0 );                   // base → active, exact (totals, fees)
API::convert( 10.0, true );             // base → active, with rounding/charm (catalog prices)
API::convertBack( 9.2 );                // active → base
API::switchTo( 'EUR' );                 // switch the visitor (validates + sets cookie)
API::manager();                         // full CurrencyManager for advanced use
```

Wait for the plugin to boot before calling it from your own bootstrap:

```php
add_action( 'u2code_mc/loaded', function ( $container ) {
    // All services are constructed and hooked from here on.
} );
```

## Converting your plugin's amounts

If your extension adds monetary amounts (fees, addon prices, thresholds)
stored in the base currency, pipe them through the generic filter — it is a
no-op when the visitor is browsing in the base currency:

```php
$amount = apply_filters( 'u2code_mc/convert_price', $amount );
```

## Filters

| Filter | Purpose |
| --- | --- |
| `u2code_mc/currencies` | Add/alter configured currencies. Receives `Currency[]` keyed by code. |
| `u2code_mc/settings` | Override plugin settings at runtime. |
| `u2code_mc/detected_currency` | Change the outcome of visitor currency detection (still validated). |
| `u2code_mc/current_currency` | Filter the active currency code per request. |
| `u2code_mc/converted_price` | Adjust every converted amount. Args: converted, original, Currency. |
| `u2code_mc/fixed_price` | Adjust a per-product fixed price. Args: value, product, prop, code. |
| `u2code_mc/should_convert` | Gate conversion per context (admin, REST, cron are off by default). |
| `u2code_mc/convert_price` | Convert a raw base-currency amount (see above). |
| `u2code_mc/rate_providers` | Register exchange-rate providers (see below). |
| `u2code_mc/integrations` | Register third-party integrations (see below). |
| `u2code_mc/switcher_args` | Change switcher rendering args (style, flags…). |
| `u2code_mc/hide_switcher` | Hide the switcher on specific pages. |

## Actions

| Action | Fires |
| --- | --- |
| `u2code_mc/loaded` | After all services boot. Receives the ServiceContainer. |
| `u2code_mc/currency_switched` | After the visitor switched currency. Args: new code, previous code. |
| `u2code_mc/rates_updated` | After a rate update run. Receives `{ updated: string[], errors: string[] }`. |
| `u2code_mc/order_meta_saved` | After the conversion snapshot was written to an order. Args: order, rate. |

## JavaScript events

```js
document.addEventListener( 'u2mc:switched', ( e ) => e.detail.currency );
document.addEventListener( 'u2mc:pricesRedrawn', ( e ) => e.detail.currency );
```

## Custom exchange-rate provider

```php
use U2Code\Multicurrency\Rates\AbstractHttpProvider;

class MyBankProvider extends AbstractHttpProvider {

    public function getId(): string {
        return 'my-bank';
    }

    public function getName(): string {
        return 'My Bank';
    }

    public function fetchRates( string $base, array $codes, string $apiKey = '' ): array {
        $data = $this->getJson( 'https://api.my-bank.example/rates?base=' . $base );

        return array_intersect_key( $data['rates'], array_flip( $codes ) );
    }
}

add_filter( 'u2code_mc/rate_providers', function ( array $providers ) {
    $providers[] = new MyBankProvider();

    return $providers;
} );
```

Throw `U2Code\Multicurrency\Rates\RateFetchException` on failure — the
updater records the message and keeps the previous rates.

## Custom integration

```php
use U2Code\Multicurrency\Integrations\AbstractIntegration;

class MyPluginIntegration extends AbstractIntegration {

    public function getSlug(): string { return 'my-plugin'; }
    public function getTitle(): string { return 'My Plugin'; }
    public function getDescription(): string { return 'Converts My Plugin fees.'; }

    public function isAvailable(): bool {
        return class_exists( '\MyPlugin' );
    }

    public function run(): void {
        add_filter( 'my_plugin/fee', function ( $fee ) {
            return apply_filters( 'u2code_mc/convert_price', $fee );
        } );
    }
}

add_filter( 'u2code_mc/integrations', function ( array $integrations, $manager ) {
    $integrations[] = new MyPluginIntegration();

    return $integrations;
}, 10, 2 );
```

Integrations only boot when `isAvailable()` is true AND the merchant has not
disabled them (option `u2code_mc_integration_{slug}`).

## Order meta

Every order placed while the plugin is active carries:

| Meta key | Value |
| --- | --- |
| `_u2code_mc_currency` | Currency the customer paid in. |
| `_u2code_mc_rate` | Effective base→paid rate at purchase time. |
| `_u2code_mc_base_currency` | The store base currency at purchase time. |
| `_u2code_mc_base_total` | Order total expressed in the base currency. |

Use the stored rate for refunds and accounting — never the current one.

## Product meta (fixed prices)

`_u2code_mc_regular_price_{CODE}` and `_u2code_mc_sale_price_{CODE}` on
products and variations. Empty/absent means "convert automatically".

`_u2code_mc_fixed_enabled` ('yes'/'no') gates whether the overrides apply for
that product. Absent = legacy behavior (apply whenever a value exists).
Unchecking the product-screen checkbox sets 'no' but keeps the stored values,
so re-enabling restores them.

## REST API

* `GET /wp-json/u2code-multicurrency/v1/context` — public: active currency,
  base, enabled currencies with rates/symbols/flags. For headless fronts.
* `GET/PUT /wp-json/u2code-multicurrency/v1/settings` — `manage_woocommerce`.
* `POST /wp-json/u2code-multicurrency/v1/rates/refresh` — `manage_woocommerce`.

## Styling hooks

The floating switcher automatically lifts itself above fixed theme bottom
bars (Storefront handheld bar, cart bars, cookie banners) by probing what
covers it. To pin the offset manually instead:

```css
.u2mc-sticky {
    --u2mc-sticky-offset: 96px;
}
```

The switcher inherits colors; its look is themeable via the custom
properties on `.u2mc-switcher` (`--u2mc-border`, `--u2mc-bg`, `--u2mc-text`,
`--u2mc-accent`, `--u2mc-radius`).

## Frontend endpoints (WooCommerce AJAX)

* `POST /?wc-ajax=u2mc_switch_currency` with `currency=EUR` — sets the cookie.
* `POST /?wc-ajax=u2mc_prices` with `ids[]` — current price HTML per product
  (used by cache compatibility mode).
