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 @@ Stash Unity Logo

-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.