The stash-native package makes it simple to add Stash in-app purchases (IAPs) and webshops to your game or app. It delivers seamless, native-like payment flows and selection dialogs, which appear as system dialogs on Android and iOS through lightweight embedded webviews, while providing direct callbacks to your application.
| Platform | Description |
|---|---|
| Android | Android library (AAR). |
| iOS | iOS framework (XCFramework). |
If you're using one of the game engines listed below, we offer dedicated wrappers for this library. These wrappers provide ready-to-use interfaces for integrating Stash features into your project, along with added development toolsβsuch as full flow testing directly in the Unity Editor.
| Engine | Repository | Compatibility | |
|---|---|---|---|
![]() |
Unity | stash-unity | Unity 2019.4+ (LTS recommended) |
![]() |
Unreal Engine 5 | stash-unreal (main) | Unreal Engine 5.0+ |
![]() |
Unreal Engine 4 | stash-unreal (4.27-plus) | Unreal Engine 4.27-plus |
Latest pre-built binaries are always available on Releases Page:
- Android:
stashnative-release.aar(orStashNative-<tag>.aarfrom releases) - iOS:
StashNative.xcframework.zip
Both platforms contain up-to-date sample apps that demonstrate the library usage and functions. You can run them from source
- Android:
./Android/sample/- Run with./gradlew :sample:installDebug - iOS:
./iOS/Sample/- OpenStashNativeSample.xcodeprojin Xcode
or try them instantly in your browser using the Appetize online emulator:
- Download
StashNative-<tag>.aarfrom GitHub Releases and add it to your project (e.g.libs/). - In your app's
build.gradle:
dependencies {
implementation files('libs/StashNative-<tag>.aar')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.browser:browser:1.7.0'
}To build the AAR locally: cd Android && ./gradlew :stashnative:assembleRelease (output in stashnative/build/outputs/aar/).
XCFramework (recommended): Download StashNative.xcframework.zip from GitHub Releases, unzip it, add StashNative.xcframework to your Xcode project, and under Frameworks, Libraries, and Embedded Content set it to Embed & Sign.
Swift Package Manager: In Xcode choose File β Add Packages... and add https://github.com/stashgg/stash-native.git, then select the StashNative package for your target.
Manual integration: Copy all files from StashNative/Sources/StashNative/ into your project, add them to your target, and link SafariServices.framework and WebKit.framework.
The library provides three distinct ways to present Stash URLs within your app or game: openCard, openModal, and openBrowser. Each method lets you present different types of Stash experiencesβsuch as Stash Pay checkout, Stash Web Shop, or Stash Opt-inβin a style that best fits your user flow. Details for each option are provided below.
Drawer-style card: slides up from the bottom on phones, centered on tablets. Suited for Stash Pay payment links or channel selection. Integrating Stash Pay
Android
StashNativeCard.CardConfig config = new StashNativeCard.CardConfig(); // or null for defaults
StashNativeCard.getInstance().openCard("https://testcard.stashpreview.com", config);iOS (Swift)
let config = StashNativeCardConfig() // or nil for defaults
StashNativeCard.sharedInstance().openCard(withURL: "https://testcard.stashpreview.com", config: config)iOS (Objective-C)
StashNativeCardConfig *config = [[StashNativeCardConfig alloc] init]; // or nil for defaults
[[StashNativeCard sharedInstance] openCardWithURL:@"https://testcard.stashpreview.com" config:config];Pass a CardConfig (or nil/null) to control orientation and sizing. Pass nil/null for defaults.
| Aspect | Description |
|---|---|
| forcePortrait | true: card opens portrait-locked (separate activity on Android, portrait-only on iOS). false (default): card appears in current orientation as an overlay. |
| Phone | cardHeightRatioPortrait, cardWidthRatioLandscape, cardHeightRatioLandscape (0.1β1.0). |
| Tablet | tabletWidthRatioPortrait, tabletHeightRatioPortrait, tabletWidthRatioLandscape, tabletHeightRatioLandscape (0.1β1.0). |
Warning: If using
forcePortrait, ensure your app supports portrait or can unlock to portrait while the card is shown.
Android
StashNativeCard.CardConfig config = new StashNativeCard.CardConfig();
config.forcePortrait = false;
config.cardHeightRatioPortrait = 0.68f;
// ... tabletWidthRatioPortrait, tabletHeightRatioPortrait, etc. (see table above)
stashNative.openCard(url, config);iOS (Swift)
let config = StashNativeCardConfig()
config.forcePortrait = false
config.cardHeightRatioPortrait = 0.68
// ... tabletWidthRatioPortrait, tabletHeightRatioPortrait, etc. (see table above)
stashNative.openCard(withURL: url, config: config)| Event | Description |
|---|---|
| Payment Success | Called when the payment completes successfully. |
| Payment Failure | Called when the payment fails. |
| Dialog Dismissed | Called when the user dismisses the dialog. |
| Opt-In Response | Called when a channel selection response is received. |
| Page Loaded | Called when the page finishes loading (with load time). |
| Network Error | Called when the page load fails (no connection, HTTP error, timeout). |
Set a listener (Android) or delegate (iOS) before calling openCard or openModal. Same callback interface is used for both.
Android β implement StashNativeCardListener (or extend StashNativeCardListenerAdapter to override only the callbacks you need):
StashNativeCard.getInstance().setActivity(this);
StashNativeCard.getInstance().setListener(new StashNativeCard.StashNativeCardListener() {
@Override
public void onPaymentSuccess() {
// Handle successful payment
}
@Override
public void onPaymentFailure() {
// Handle failed payment
}
@Override
public void onDialogDismissed() {
// User closed the card/modal
}
@Override
public void onOptInResponse(String optinType) {
// Channel selection response (e.g. "email", "sms")
}
@Override
public void onPageLoaded(long loadTimeMs) {
// Page finished loading
}
@Override
public void onNetworkError() {
// Load failed (no connection, HTTP error, or timeout)
}
});iOS (Swift) β set the delegate and implement StashNativeCardDelegate (all methods are optional):
StashNativeCard.sharedInstance().delegate = self
// In your class (e.g. ViewController):
extension YourViewController: StashNativeCardDelegate {
func stashNativeCardDidCompletePayment() {
// Handle successful payment
}
func stashNativeCardDidFailPayment() {
// Handle failed payment
}
func stashNativeCardDidDismiss() {
// User closed the card/modal
}
func stashNativeCardDidReceiveOpt(in optinType: String) {
// Channel selection response
}
func stashNativeCardDidLoadPage(_ loadTimeMs: Double) {}
func stashNativeCardDidEncounterNetworkError() {
// Load failed (no connection, HTTP error, or timeout)
}
}iOS (Objective-C) β set the delegate and implement the optional protocol methods:
[StashNativeCard sharedInstance].delegate = self;
// In your class:
- (void)stashNativeCardDidCompletePayment {
// Handle successful payment
}
- (void)stashNativeCardDidFailPayment {
// Handle failed payment
}
- (void)stashNativeCardDidDismiss {
// User closed the card/modal
}
- (void)stashNativeCardDidReceiveOptIn:(NSString *)optinType {
// Channel selection response
}
- (void)stashNativeCardDidLoadPage:(double)loadTimeMs {}
- (void)stashNativeCardDidEncounterNetworkError {
// Load failed
}Centered modal on all devices. Same layout on phone and tablet; resizes on rotation. Suited for channel selection or an alternative checkout style. Stash Pay Opt-In
Android
StashNativeCard.ModalConfig config = new StashNativeCard.ModalConfig(); // or null for defaults
StashNativeCard.getInstance().openModal("https://testcard.stashpreview.com", config);iOS (Swift)
let config = StashNativeModalConfig() // or nil for defaults
StashNativeCard.sharedInstance().openModal(withURL: "https://testcard.stashpreview.com", config: config)iOS (Objective-C)
StashNativeModalConfig *config = [[StashNativeModalConfig alloc] init]; // or nil for defaults
[[StashNativeCard sharedInstance] openModalWithURL:@"https://testcard.stashpreview.com" config:config];Pass a ModalConfig (or nil/null) to control drag bar, dismiss behavior, and sizing. Pass nil/null for defaults.
| Aspect | Description |
|---|---|
| Behavior | showDragBar (default true), allowDismiss (default true). |
| Phone | phoneWidthRatioPortrait, phoneHeightRatioPortrait, phoneWidthRatioLandscape, phoneHeightRatioLandscape (0.1β1.0). |
| Tablet | tabletWidthRatioPortrait, tabletHeightRatioPortrait, tabletWidthRatioLandscape, tabletHeightRatioLandscape (0.1β1.0). |
Android
StashNativeCard.ModalConfig config = new StashNativeCard.ModalConfig();
config.showDragBar = true;
config.allowDismiss = true;
// ... phoneWidthRatioPortrait, phoneHeightRatioPortrait, tablet ratios, etc. (see table above)
stashNative.openModal(url, config);iOS (Swift)
let config = StashNativeModalConfig()
config.showDragBar = true
config.allowDismiss = true
// ... phoneWidthRatioPortrait, phoneHeightRatioPortrait, tablet ratios, etc. (see table above)
stashNative.openModal(withURL: url, config: config)Same as openCard: same events and the same listener/delegate. Set it once as shown in the Callbacks section under openCard; it receives events for both card and modal.
Opens the URL in the platform browser (Chrome Custom Tabs on Android, SFSafariViewController on iOS). No in-app UI, no config, no callbacks. Use when you only need a simple browser view.
Android
StashNativeCard.getInstance().openBrowser("https://testcard.stashpreview.com");iOS (Swift)
StashNativeCard.sharedInstance().openBrowser(withURL: "https://testcard.stashpreview.com")
// Optionally dismiss when handling a deeplink:
StashNativeCard.sharedInstance().closeBrowser()iOS (Objective-C)
[[StashNativeCard sharedInstance] openBrowserWithURL:@"https://testcard.stashpreview.com"];
// Optionally dismiss when handling a deeplink:
[[StashNativeCard sharedInstance] closeBrowser];On iOS, closeBrowser() dismisses the Safari view. On Android, closeBrowser() is a no-op (Chrome Custom Tabs cannot be closed by the app).
This package follows Semantic Versioning (major.minor.patch):
- Major: Breaking changes
- Minor: New features (backward compatible)
- Patch: Bug fixes
- Documentation: https://docs.stash.gg
- Email: [email protected]
| Attribute | Requirement |
|---|---|
| Minimum SDK | API 21 (Android 5.0 Lollipop) |
| Target SDK | API 34 (Android 14) |
| Compile SDK | 34 |
| Java Version | Java 8 (source/target), JDK 17 for build |
| Architecture | armeabi-v7a, arm64-v8a, x86, x86_64 |
| Android Version | API Level | Status | Notes |
|---|---|---|---|
| Android 14 (Upside Down Cake) | 34 | Full | Target SDK |
| Android 13 (Tiramisu) | 33 | Full | |
| Android 12/12L | 31-32 | Full | |
| Android 11 | 30 | Full | Enhanced window insets |
| Android 10 | 29 | Full | Dark mode support |
| Android 9 (Pie) | 28 | Full | |
| Android 8/8.1 (Oreo) | 26-27 | Full | |
| Android 7/7.1 (Nougat) | 24-25 | Full | |
| Android 6 (Marshmallow) | 23 | Full | |
| Android 5/5.1 (Lollipop) | 21-22 | Full | Minimum SDK |
| Android 4.4 and below | <=20 | Not Supported |
| Vendor / Skin | Compatibility | WebView Source | Notes |
|---|---|---|---|
| Google Pixel / Stock Android | Full | Google WebView (Play Store updates) | Reference implementation |
| Samsung (One UI / TouchWiz) | Full | Samsung Internet / Chrome WebView | No known issues |
| Xiaomi (MIUI) | Full | Chrome WebView | Some MIUI versions show battery optimization warnings |
| OnePlus (OxygenOS) | Full | Chrome WebView | Stock-like behavior |
| Oppo (ColorOS) | Full | Chrome WebView | |
| Vivo (Funtouch OS) | Full | Chrome WebView | |
| Realme (Realme UI) | Full | Chrome WebView | |
| Huawei (EMUI, pre-2019) | Full | Google WebView | Devices with GMS |
| Huawei (HarmonyOS/EMUI, 2019+) | Partial | Huawei WebView | No GMS; Chrome Custom Tabs unavailable; in-app WebView works |
| Honor (post-Huawei) | Full | Chrome WebView | Devices with GMS |
| Nokia (Android One) | Full | Google WebView | Stock Android |
| Motorola | Full | Chrome WebView | Near-stock Android |
| LG | Full | Chrome WebView | Legacy devices supported |
| Sony Xperia | Full | Chrome WebView | |
| ASUS (ZenUI) | Full | Chrome WebView | |
| Android Go Edition | Supported | Chrome WebView | Limited memory; may experience slower load times |
| Amazon Fire OS | Partial | Amazon WebView | Non-standard WebView; openCard/openModal work; openBrowser falls back to system browser |
| Dependency | Version | Required | Purpose |
|---|---|---|---|
| androidx.appcompat:appcompat | 1.6.1+ | Yes | Activity/Fragment support |
| androidx.browser:browser | 1.7.0+ | Yes | Chrome Custom Tabs (openBrowser) |
| Feature | Min API | Notes |
|---|---|---|
| In-app WebView (openCard, openModal) | 21 | Core functionality |
| Third-party cookies | 21 | Required for payment flows |
| Chrome Custom Tabs (openBrowser) | 21 | Requires compatible browser |
| Automatic dark mode detection | 29 | Falls back to light theme on older versions |
| Edge-to-edge display | 30 | Graceful fallback on older versions |
Core functionality (slide-up card, modal, WebView, animations, payment callbacks) works identically across all supported Android versions (API 21+). The following features have graceful fallbacks on older versions:
API 21-28 (Android 5.0-9.0)
- Dark mode: Not automatically detected. Pass
theme=darkortheme=lightas a URL parameter to control appearance. - Window insets: Uses legacy status bar handling.
API 29+ (Android 10+)
- Dark mode: Automatically detected from system settings via
Configuration.UI_MODE_NIGHT_MASK.
API 30+ (Android 11+)
- Edge-to-edge: Uses
WindowInsetsAPI for proper safe area handling on devices with rounded corners or camera cutouts.
| Attribute | Requirement |
|---|---|
| Minimum iOS | iOS 13.0 |
| Swift Version | 5.5+ |
| Xcode | 13.0+ |
| Architecture | arm64, arm64e (devices), x86_64 (simulator) |
| iOS Version | Status | Notes |
|---|---|---|
| iOS 18.x | Full | Latest |
| iOS 17.x | Full | |
| iOS 16.x | Full | |
| iOS 15.x | Full | |
| iOS 14.x | Full | |
| iOS 13.x | Full | Minimum version |
| iOS 12 and below | Not Supported |
| Device Type | Status | Notes |
|---|---|---|
| iPhone (all models iOS 13+) | Full | Portrait/landscape, card slides from bottom |
| iPad | Full | Centered presentation, all orientations |
| iPad (Split View / Slide Over) | Full | Responsive layout |
| Mac (Catalyst) | Untested | Should work; not officially tested |
| Framework | Required | Purpose |
|---|---|---|
| WebKit | Yes | WKWebView for in-app checkout |
| SafariServices | Yes | SFSafariViewController (openBrowser) |
| Foundation | Yes | Core framework |
| UIKit | Yes | UI components |
| Language | Status | Notes |
|---|---|---|
| Swift | Full | Native API |
| Objective-C | Full | Native API |
| ARC | Full | Automatic Reference Counting |
| Non-ARC | Full | Manual memory management (Unreal Engine compatibility) |
Android
- Huawei (2019+ without GMS): openBrowser uses system browser instead of Chrome Custom Tabs; other features work normally
- Android Go: Performance may vary on low-memory devices (<1GB RAM)
- WebView Updates: Devices without Play Store may have outdated WebView; recommend users update Android System WebView
iOS
- iOS 13 Dark Mode: Requires explicit theme parameter in URL; automatic detection not available on iOS 13.0-13.3


