From 7616e15566c269cf7cd267049ace8b7e1a393942 Mon Sep 17 00:00:00 2001
From: Richard Elms
Date: Wed, 26 Aug 2026 12:42:01 +0200
Subject: [PATCH] Document desktop support, 2.4.0 changelog, sample prewarm and
desktop links
README: requirements, package structure, a Desktop (Windows / macOS)
section, API table entries for Prewarm and SetInspectableWebViewsEnabled,
desktop notes on browser callbacks and ignored ratios, the simulator
section. TROUBLESHOOTING: WebView2 runtime missing, DLL / bundle not
found, DLL locked by the editor, Gatekeeper, dark card. CHANGELOG 2.4.0
and package.json 2.4.0 (description, keywords). Sample: Prewarm at
startup and checkout links generated without the platform field on
desktop, in both the Samples~ and Assets copies.
---
Assets/StashLinkGenerator.cs | 57 +++++++++++--------
Assets/StashSample.cs | 3 +
Packages/gg.stash.unity/CHANGELOG.md | 19 +++++++
.../StashSample/StashLinkGenerator.cs | 57 +++++++++++--------
.../Samples~/StashSample/StashSample.cs | 3 +
Packages/gg.stash.unity/package.json | 9 ++-
README.md | 40 ++++++++++---
TROUBLESHOOTING.md | 24 ++++++++
8 files changed, 150 insertions(+), 62 deletions(-)
diff --git a/Assets/StashLinkGenerator.cs b/Assets/StashLinkGenerator.cs
index e3ee5fd..d313a14 100644
--- a/Assets/StashLinkGenerator.cs
+++ b/Assets/StashLinkGenerator.cs
@@ -32,38 +32,43 @@ public void RequestCheckoutUrl(Action onUrl, Action onError)
private IEnumerator RequestCheckoutUrlCoroutine(Action onUrl, Action onError)
{
- // Determine purchase platform dynamically
- string platformValue;
+ var item = new QuickPayItem
+ {
+ id = "test_item_id",
+ pricePerItem = "0.99",
+ quantity = 1,
+ imageUrl = "https://storage.googleapis.com/stash_assets/stash_logo_128.png",
+ name = "Test Item",
+ description = "A description of the test item"
+ };
+
+ // The platform enum only knows IOS and ANDROID. Desktop players omit the field (UNDEFINED, the
+ // web channel with every wallet enabled); an unknown value would be dropped anyway.
+ string json;
+#if UNITY_IOS || UNITY_ANDROID
#if UNITY_IOS
- platformValue = "IOS";
-#elif UNITY_ANDROID
- platformValue = "ANDROID";
+ const string platformValue = "IOS";
#else
- platformValue = Application.platform.ToString().ToUpper();
+ const string platformValue = "ANDROID";
#endif
-
- var request = new QuickPayRequest
+ json = JsonUtility.ToJson(new QuickPayRequest
{
regionCode = "USA",
currency = "USD",
- item = new QuickPayItem
- {
- id = "test_item_id",
- pricePerItem = "0.99",
- quantity = 1,
- imageUrl = "https://storage.googleapis.com/stash_assets/stash_logo_128.png",
- name = "Test Item",
- description = "A description of the test item"
- },
- user = new QuickPayUser
- {
- id = "test_user_id",
- validatedEmail = "test@domain.com",
- platform = platformValue
- }
- };
+ item = item,
+ user = new QuickPayUser { id = "test_user_id", validatedEmail = "test@domain.com", platform = platformValue }
+ });
+#else
+ json = JsonUtility.ToJson(new QuickPayDesktopRequest
+ {
+ regionCode = "USA",
+ currency = "USD",
+ item = item,
+ user = new QuickPayDesktopUser { id = "test_user_id", validatedEmail = "test@domain.com" }
+ });
+#endif
- byte[] body = Encoding.UTF8.GetBytes(JsonUtility.ToJson(request));
+ byte[] body = Encoding.UTF8.GetBytes(json);
using (var www = new UnityWebRequest(QUICK_PAY_ENDPOINT, "POST"))
{
www.uploadHandler = new UploadHandlerRaw(body);
@@ -181,6 +186,8 @@ private static string FormatRequestError(UnityWebRequest www)
[Serializable] private class QuickPayRequest { public string regionCode; public string currency; public QuickPayItem item; public QuickPayUser user; }
[Serializable] private class QuickPayItem { public string id; public string pricePerItem; public int quantity; public string imageUrl; public string name; public string description; }
[Serializable] private class QuickPayUser { public string id; public string validatedEmail; public string regionCode; public string platform; }
+ [Serializable] private class QuickPayDesktopRequest { public string regionCode; public string currency; public QuickPayItem item; public QuickPayDesktopUser user; }
+ [Serializable] private class QuickPayDesktopUser { public string id; public string validatedEmail; public string regionCode; }
[Serializable] private class QuickPayResponse { public string id; public string url; public string regionCode; }
[Serializable] private class AuthenticatedUrlRequest { public AuthenticatedUrlUser user; public string target; }
[Serializable] private class AuthenticatedUrlUser { public string id; }
diff --git a/Assets/StashSample.cs b/Assets/StashSample.cs
index 5b470dd..3f94fd5 100644
--- a/Assets/StashSample.cs
+++ b/Assets/StashSample.cs
@@ -15,6 +15,9 @@ public class StashSample : MonoBehaviour
private void Awake()
{
+ // Desktop players: start the browser processes now so the first checkout opens instantly (no-op on mobile).
+ StashNative.Instance.Prewarm();
+
StashNative.Instance.OnExternalPayment += url =>
SetStatus("External payment" + url);
diff --git a/Packages/gg.stash.unity/CHANGELOG.md b/Packages/gg.stash.unity/CHANGELOG.md
index 53b8fe2..f8febfd 100644
--- a/Packages/gg.stash.unity/CHANGELOG.md
+++ b/Packages/gg.stash.unity/CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [2.4.0] - 2026-08-26
+
+### Added
+
+- **Windows and macOS players.** `OpenCard`, `OpenModal` and `OpenBrowser` now work in Standalone Windows (x64, WebView2 Evergreen runtime) and macOS (11+, universal) players through the **Embedded Stash Native SDK [2.4.0](https://github.com/stashgg/stash-native/releases/tag/2.4.0)** desktop hosts (`Plugins/Windows/x86_64/StashNativeDesktop.dll`, `Plugins/macOS/StashNativeDesktop.bundle`). The checkout is a card over the game window; the same callbacks, per-call callbacks and config structs apply. Ratio fields are ignored on desktop (fixed 480 x 720 pt card, 480 x 600 pt modal). `OnBrowserClosed` is not raised on desktop.
+- **`StashNative.Prewarm()`** (desktop: creates the browser processes ahead of the first checkout) and **`StashNative.SetInspectableWebViewsEnabled(bool)`** (desktop: Safari Web Inspector / Edge DevTools on the checkout webview). No-ops elsewhere.
+- Exclusive fullscreen on Windows is switched to borderless for the checkout and restored afterwards.
+- EditMode tests (`Tests/Editor`) and a `test.yml` workflow; `main.yml` builds `StandaloneWindows64` and `StandaloneOSX`.
+
+### Changed
+
+- **Editor simulator** presents card and modal flows through the desktop host in a standalone window (device presets kept, card config no longer dropped, callbacks routed like on a device). Play mode with a Standalone build target opens the desktop host with the desktop sizing.
+- **Sample**: `StashLinkGenerator` omits `platform` for desktop players (the API enum only knows `IOS` / `ANDROID`); `StashSample` calls `Prewarm()` at startup.
+- Package now targets Android, iOS, Windows and macOS (`package.json` description and keywords).
+
+### Removed
+
+- The editor-only WebView natives `Editor/Windows` (WebView2 launcher DLL) and `Editor/macOS` (WKWebView launcher bundle) and their `Stash.Editor.Windows.WebViewLauncher` / `Stash.Editor.macOS.WebViewLauncher` types. The simulator uses the shipped desktop hosts instead.
+
## [2.3.0] - 2026-07-28
### Changed
diff --git a/Packages/gg.stash.unity/Samples~/StashSample/StashLinkGenerator.cs b/Packages/gg.stash.unity/Samples~/StashSample/StashLinkGenerator.cs
index e3ee5fd..d313a14 100644
--- a/Packages/gg.stash.unity/Samples~/StashSample/StashLinkGenerator.cs
+++ b/Packages/gg.stash.unity/Samples~/StashSample/StashLinkGenerator.cs
@@ -32,38 +32,43 @@ public void RequestCheckoutUrl(Action onUrl, Action onError)
private IEnumerator RequestCheckoutUrlCoroutine(Action onUrl, Action onError)
{
- // Determine purchase platform dynamically
- string platformValue;
+ var item = new QuickPayItem
+ {
+ id = "test_item_id",
+ pricePerItem = "0.99",
+ quantity = 1,
+ imageUrl = "https://storage.googleapis.com/stash_assets/stash_logo_128.png",
+ name = "Test Item",
+ description = "A description of the test item"
+ };
+
+ // The platform enum only knows IOS and ANDROID. Desktop players omit the field (UNDEFINED, the
+ // web channel with every wallet enabled); an unknown value would be dropped anyway.
+ string json;
+#if UNITY_IOS || UNITY_ANDROID
#if UNITY_IOS
- platformValue = "IOS";
-#elif UNITY_ANDROID
- platformValue = "ANDROID";
+ const string platformValue = "IOS";
#else
- platformValue = Application.platform.ToString().ToUpper();
+ const string platformValue = "ANDROID";
#endif
-
- var request = new QuickPayRequest
+ json = JsonUtility.ToJson(new QuickPayRequest
{
regionCode = "USA",
currency = "USD",
- item = new QuickPayItem
- {
- id = "test_item_id",
- pricePerItem = "0.99",
- quantity = 1,
- imageUrl = "https://storage.googleapis.com/stash_assets/stash_logo_128.png",
- name = "Test Item",
- description = "A description of the test item"
- },
- user = new QuickPayUser
- {
- id = "test_user_id",
- validatedEmail = "test@domain.com",
- platform = platformValue
- }
- };
+ item = item,
+ user = new QuickPayUser { id = "test_user_id", validatedEmail = "test@domain.com", platform = platformValue }
+ });
+#else
+ json = JsonUtility.ToJson(new QuickPayDesktopRequest
+ {
+ regionCode = "USA",
+ currency = "USD",
+ item = item,
+ user = new QuickPayDesktopUser { id = "test_user_id", validatedEmail = "test@domain.com" }
+ });
+#endif
- byte[] body = Encoding.UTF8.GetBytes(JsonUtility.ToJson(request));
+ byte[] body = Encoding.UTF8.GetBytes(json);
using (var www = new UnityWebRequest(QUICK_PAY_ENDPOINT, "POST"))
{
www.uploadHandler = new UploadHandlerRaw(body);
@@ -181,6 +186,8 @@ private static string FormatRequestError(UnityWebRequest www)
[Serializable] private class QuickPayRequest { public string regionCode; public string currency; public QuickPayItem item; public QuickPayUser user; }
[Serializable] private class QuickPayItem { public string id; public string pricePerItem; public int quantity; public string imageUrl; public string name; public string description; }
[Serializable] private class QuickPayUser { public string id; public string validatedEmail; public string regionCode; public string platform; }
+ [Serializable] private class QuickPayDesktopRequest { public string regionCode; public string currency; public QuickPayItem item; public QuickPayDesktopUser user; }
+ [Serializable] private class QuickPayDesktopUser { public string id; public string validatedEmail; public string regionCode; }
[Serializable] private class QuickPayResponse { public string id; public string url; public string regionCode; }
[Serializable] private class AuthenticatedUrlRequest { public AuthenticatedUrlUser user; public string target; }
[Serializable] private class AuthenticatedUrlUser { public string id; }
diff --git a/Packages/gg.stash.unity/Samples~/StashSample/StashSample.cs b/Packages/gg.stash.unity/Samples~/StashSample/StashSample.cs
index c8e7acc..8655cc4 100644
--- a/Packages/gg.stash.unity/Samples~/StashSample/StashSample.cs
+++ b/Packages/gg.stash.unity/Samples~/StashSample/StashSample.cs
@@ -13,6 +13,9 @@ public class StashSample : MonoBehaviour
private void Awake()
{
+ // Desktop players: start the browser processes now so the first checkout opens instantly (no-op on mobile).
+ StashNative.Instance.Prewarm();
+
StashNative.Instance.OnExternalPayment += url =>
SetStatus("External payment" + url);
}
diff --git a/Packages/gg.stash.unity/package.json b/Packages/gg.stash.unity/package.json
index 0581252..0c6de5f 100644
--- a/Packages/gg.stash.unity/package.json
+++ b/Packages/gg.stash.unity/package.json
@@ -1,17 +1,20 @@
{
"name": "gg.stash.unity",
"displayName": "Stash for Unity",
- "version": "2.3.0",
+ "version": "2.4.0",
"unity": "2021.3",
"unityRelease": "0f1",
- "description": "Native-feeling Stash Pay IAP checkout and webshop presentation directly inside your Unity game (Android/iOS).",
+ "description": "Native-feeling Stash Pay IAP checkout and webshop presentation directly inside your Unity game (Android, iOS, Windows, macOS).",
"keywords": [
"stash",
"iap",
"d2c",
"checkout",
"webshop",
- "payments"
+ "payments",
+ "desktop",
+ "windows",
+ "macos"
],
"author": {
"name": "Stash",
diff --git a/README.md b/README.md
index f1d623a..9f4450e 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,13 @@
-Unity package wrapper for [stash-native](https://github.com/stashgg/stash-native) (embedded **Stash Native 2.3.0**), enabling native-feeling Stash Pay IAP checkout and webshop presentation directly inside your Unity game (Android/iOS).
+Unity package wrapper for [stash-native](https://github.com/stashgg/stash-native) (embedded **Stash Native 2.4.0**), enabling native-feeling Stash Pay IAP checkout and webshop presentation directly inside your Unity game (Android, iOS, Windows, macOS).
## Requirements
- Unity 2021.3+ (LTS recommended)
- iOS 13.0+ / Android API 21+
+- Windows 10 1809+ or Windows 11 (x64) with the WebView2 Evergreen runtime (preinstalled on Windows 11 and updated Windows 10) / macOS 11+ (arm64 and x86_64)
## Installation (UPM)
@@ -84,9 +85,13 @@ When installed via UPM, the package lives under `Packages/gg.stash.unity/` with
| Path | Description |
|------|-------------|
| **Runtime/** | **`StashNative.cs`** – Singleton API: `OpenCard`, `OpenModal`, `OpenBrowser`, `CloseBrowser`, Android keep-alive helpers, and events. |
-| **Editor/** | **(Optional)** Editor window for testing card/modal flows in the Unity Editor (Windows and macOS). |
+| **Runtime/Desktop/** | `StashNativeDesktopBridge.cs`: binding to the desktop hosts' C ABI (Windows and macOS players and editors). |
+| **Editor/** | **(Optional)** Editor window for testing card/modal flows in the Unity Editor (Windows and macOS), presented through the desktop host. |
| **Plugins/Android/** | StashNative AAR and Unity bridge for Android. |
| **Plugins/iOS/** | Unity bridge and StashNative.xcframework for iOS. |
+| **Plugins/Windows/x86_64/** | `StashNativeDesktop.dll` (stash-native Windows host, WebView2). |
+| **Plugins/macOS/** | `StashNativeDesktop.bundle` (stash-native macOS host, WKWebView, universal). |
+| **Tests/Editor/** | EditMode tests for the desktop bridge. |
| **Samples~/StashSample/** | Optional sample (on disk only). Unity **does not list** files under `Samples~` in the Project window (the `~` path is excluded from import). Use **Package Manager → Samples → Import** to copy them into `Assets/`. |
@@ -195,6 +200,21 @@ StashNative.Instance.OpenModal(
All callbacks and config are optional; you can pass only the ones you need or use the global events instead. See the API reference below for more details about configuration options.
+### Desktop (Windows / macOS)
+
+Standalone Windows and macOS players use the same `OpenCard` / `OpenModal` / `OpenBrowser` calls, callbacks and config structs. The checkout is presented as a card over the game window (the game keeps rendering underneath) through the stash-native desktop hosts vendored in `Plugins/Windows/x86_64` and `Plugins/macOS`; nothing is bundled, Windows uses the WebView2 Evergreen runtime and macOS the system WebKit.
+
+- **Sizing**: the card is a fixed 480 x 720 pt surface and the modal 480 x 600 pt, centred and clamped to the game window; the ratio fields of the config structs are accepted and ignored, `forcePortrait` has no effect.
+- **`OpenBrowser`** opens the system browser; `OnBrowserClosed` never fires and `CloseBrowser` is a no-op on desktop.
+- **Prewarm**: call `StashNative.Instance.Prewarm()` once at startup (a no-op on mobile) so the first checkout opens instantly.
+- **Fullscreen**: on Windows the package switches an exclusive-fullscreen game to borderless for the checkout and restores it afterwards.
+- **Threading**: events are delivered from `StashNative`'s `Update`, on the game thread.
+- **Apple Pay is not available in the macOS card** (a WebKit limitation for embedded web views; the checkout hides the button). Cards, Google Pay and PayPal are. Everything, Apple Pay included, is available on Windows.
+- **Links**: generate checkout links without the `platform` field for desktop players (the sample's `StashLinkGenerator` does this); saved payment methods are keyed to the user id you pass.
+- **Shutdown**: the package releases the webview environment when the application quits and, in the editor, before assembly reloads and when leaving play mode.
+
+Requirements and troubleshooting: see [Requirements](#requirements) and [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
+
### OpenBrowser()
Opens the URL in the platform browser (Chrome Custom Tabs on Android, SFSafariViewController on iOS). Use when you need an alternative lightweight, system-native browser view.
@@ -259,8 +279,10 @@ All public API lives on the **`StashNative`** singleton. Access it via **`StashN
|-----------|-------------|
| **`void OpenCard(string url, Action dismissCallback, Action successCallback, Action failureCallback, StashNativeCardConfig? config)`** | Opens the URL in the native card (drawer). `successCallback` receives an optional order payload (may be empty). All callbacks and config are optional. |
| **`void OpenModal(string url, Action dismissCallback, Action successCallback, Action failureCallback, StashNativeModalConfig? config)`** | Opens the URL in a centered modal. Same success signature as `OpenCard`. |
-| **`void OpenBrowser(string url)`** | Opens the URL in the platform browser (Chrome Custom Tabs when `androidx.browser` is on the classpath, otherwise the system browser on Android; `SFSafariViewController` on iOS). |
-| **`void CloseBrowser()`** | **iOS only:** dismisses the Safari view programmatically. No-op on Android. |
+| **`void OpenBrowser(string url)`** | Opens the URL in the platform browser (Chrome Custom Tabs when `androidx.browser` is on the classpath, otherwise the system browser on Android; `SFSafariViewController` on iOS; the system browser on Windows and macOS). |
+| **`void CloseBrowser()`** | **iOS only:** dismisses the Safari view programmatically. No-op on Android and desktop. |
+| **`void Prewarm()`** | **Desktop only:** creates the browser processes ahead of the first checkout so it opens instantly. No-op elsewhere. |
+| **`void SetInspectableWebViewsEnabled(bool enabled)`** | **Desktop only:** makes the checkout webviews inspectable (Safari Web Inspector / Edge DevTools). Debug builds only. |
| **`void SetKeepAliveEnabled(bool enabled)`** | **Android only:** opt in to the SDK keep-alive foreground service during external browser flows. |
| **`void SetKeepAliveConfig(StashNativeKeepAliveConfig config)`** | **Android only:** notification title, text, and optional icon resource id (`0` = library default). |
| **`void Dismiss()`** | Dismisses the current card or modal. |
@@ -279,7 +301,7 @@ Subscribe on `StashNative.Instance`.
| **`OnPaymentSuccess`** | Payment completed successfully in the in-app UI. Argument: optional order string from checkout (may be empty). |
| **`OnPaymentFailure`** | Payment failed in the in-app UI. |
| **`OnExternalPayment`** | Checkout opened an external URL (e.g. GPay, Klarna, crypto). Finalize via deeplink; same flow as described in [stash-native callbacks](https://github.com/stashgg/stash-native/blob/main/README.md). |
-| **`OnBrowserClosed`** | The external browser (Chrome Custom Tabs / SFSafariViewController) was closed after `OpenBrowser` or an external payment redirect. |
+| **`OnBrowserClosed`** | The external browser (Chrome Custom Tabs / SFSafariViewController) was closed after `OpenBrowser` or an external payment redirect. Not raised on desktop. |
| **`OnOptinResponse`** | Opt-in / channel selection response (e.g. `"stash_pay"`, `"native_iap"`). |
| **`OnPageLoaded`** | Page finished loading (argument: load time in ms). |
| **`OnNetworkError`** | Page load failed (no connection, HTTP error, timeout). |
@@ -297,14 +319,14 @@ Optional per-call config for **`OpenCard`**. **`StashNativeCardConfig.Default`**
| Field | Default | Description |
|-------|---------|-------------|
-| **`forcePortrait`** | `false` | Portrait-locked on phone when true. **Required for landscape-locked games:** Set to `true` if your Unity game is locked to landscape orientation to ensure checkout displays in portrait. Portrait orientation must be enabled in Unity Player Settings (iOS: `allowedAutorotateToPortrait: 1`). |
+| **`forcePortrait`** | `false` | Portrait-locked on phone when true. **Required for landscape-locked games:** Set to `true` if your Unity game is locked to landscape orientation to ensure checkout displays in portrait. Portrait orientation must be enabled in Unity Player Settings (iOS: `allowedAutorotateToPortrait: 1`). No effect on desktop. |
| **`cardHeightRatioPortrait`** | `0.68f` | Card height ratio portrait (0.1–1.0). |
| **`cardWidthRatioLandscape`** | `0.7f` | Card width ratio landscape. |
| **`cardHeightRatioLandscape`** | `0.9f` | Card height ratio landscape. |
| **`tabletWidthRatioPortrait`** | `0.4f` | Tablet width portrait. |
| **`tabletHeightRatioPortrait`** | `0.5f` | Tablet height portrait. |
| **`tabletWidthRatioLandscape`** | `0.3f` | Tablet width landscape. |
-| **`tabletHeightRatioLandscape`** | `0.6f` | Tablet height landscape. |
+| **`tabletHeightRatioLandscape`** | `0.6f` | Tablet height landscape. Desktop: all ratio fields are ignored (fixed 480 x 720 pt card). |
| **`autoClose`** | `true` | When `false`, the card stays open after the payment success/failure callback (callbacks still fire) until closed by the page, user, or host. |
| **`backgroundColor`** | `null` | Optional shell color (`#RGB`, `#RRGGBB`, `#AARRGGBB`). Omit for the default Stash light/dark theme. |
@@ -315,7 +337,7 @@ Optional per-call config for **`OpenModal`**. **`StashNativeModalConfig.Default`
| Field | Default | Description |
|-------|---------|-------------|
| **`allowDismiss`** | `true` | User can dismiss (tap outside / gestures per native SDK). |
-| **`phoneWidthRatioPortrait`** … **`tabletHeightRatioLandscape`** | (see struct) | Size ratios 0.1–1.0. |
+| **`phoneWidthRatioPortrait`** … **`tabletHeightRatioLandscape`** | (see struct) | Size ratios 0.1–1.0. Desktop: ignored (fixed 480 x 600 pt modal). |
| **`autoClose`** | `true` | When `false`, the modal stays open after the payment success/failure callback (callbacks still fire) until closed by the page, user, or host. |
| **`backgroundColor`** | `null` | Optional shell color; omit for default Stash theme. |
@@ -341,7 +363,7 @@ Used with **`SetKeepAliveConfig`** on **Android** only.
Package includes a Unity editor extension that allows you to test Stash URLs directly in the Unity Editor without building to a device.
-When you call `OpenCard()` or `OpenModal()` in the Editor, the extension intercepts and displays the flow in a preview window. You can interact with the UI and verify callback events. `OpenBrowser()` is not simulated; it opens the system browser.
+When you call `OpenCard()` or `OpenModal()` in play mode with a mobile build target, the extension displays the flow in a preview window sized to a phone or tablet preset (the same stash-native desktop host that ships in Windows and macOS players, in a standalone window). You can interact with the UI and verify callback events; the Simulate buttons fire the callbacks without a page. With a **Standalone** build target active, play mode opens the desktop host directly with the desktop sizing, so what you test is what ships. `OpenBrowser()` is not simulated; it opens the system browser.
> **Note:** Currently **Windows** and **macOS** versions of Unity are supported for editor simulator. Linux versions of editor are not supported.
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 3dea27b..4b0ae86 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -23,6 +23,30 @@ Or fix it in Xcode project:
Ensure `StashNative.xcframework` is present in `Packages/gg.stash.unity/Plugins/iOS/` before building from Unity so the post-process can add it to the main target’s embed phase.
+### [Windows] Checkout does not open, "WebView2 Runtime is not installed" in the log
+
+The Windows host needs the WebView2 Evergreen runtime on the player's machine. It is preinstalled on Windows 11 and on updated Windows 10; on a machine without it, `OpenCard` / `OpenModal` report `OnNetworkError` (and an `error` line in the log). Ship the [Evergreen bootstrapper](https://developer.microsoft.com/microsoft-edge/webview2/) with your installer, or fall back to `OpenBrowser`.
+
+### [Windows] "StashNativeDesktop.dll not found" in the editor or player
+
+The DLL lives in `Packages/gg.stash.unity/Plugins/Windows/x86_64/` and must be enabled for Standalone Win64 and the Windows editor in its import settings (it is by default). In a player it is copied next to the executable under `_Data/Plugins/x86_64/`. If a custom build step strips plugins, add it back.
+
+### [Windows] Editor cannot rebuild or update the package: "StashNativeDesktop.dll is being used by another process"
+
+The editor holds the DLL once loaded. Close the Unity editor before replacing the file (for example when running `Tools/sync-desktop-binaries.sh`).
+
+### [macOS] "StashNativeDesktop.bundle not found" in a built player
+
+Unity copies native plugins to `.app/Contents/PlugIns/`. The bundle's import settings must enable Standalone OSX (they do by default). If a post-build step re-signs or re-packages the app, keep `Contents/PlugIns/StashNativeDesktop.bundle` in place.
+
+### [macOS] Gatekeeper or notarization complaints about the bundle
+
+The bundle inside the package is unsigned; signing applies to the hosting app. When you sign and notarize your player, the bundle in `Contents/PlugIns` is signed as part of the app (`codesign --deep` or your own per-file signing) and passes notarization. Ad-hoc signed development builds run locally without extra steps.
+
+### [Desktop] The card stays dark / the page never loads in the editor
+
+The editor presents the checkout through the same host as the players in a standalone window. A `networkError` after about 15 seconds means the page could not be reached; `http://` URLs are refused (checkout must be `https://`). Use the Simulate buttons in the test window to exercise callbacks without a page.
+
### [Android] Bridge does not compile
The Unity bridge expects the StashNative AAR to expose `StashNative` and related classes. If your AAR uses a different Java package than `com.stash.stashnative`, update the fully qualified class names in `StashNativeCardUnityBridge.java` to match the AAR.