flutter_windows_iap
A Flutter plugin that exposes Microsoft Store in-app purchase APIs for Windows — no bundled UI.
Fetch add-on products, initiate purchases, verify licenses, and resolve One Store purchase/collection IDs — then build your own paywall and entitlement logic.
Main highlight — native Microsoft Store integration
Built on Windows.Services.Store (StoreContext) so your Flutter Windows app can use the same add-on catalog, purchase flow, and license checks as a native UWP/WinUI app — Consumable, Durable, and UnmanagedConsumable add-ons, including subscriptions configured as durable products in Partner Center.
| Platform | Windows (Microsoft Store / MSIX packaged app) |
|---|---|
| Dart | >=3.7.0 <4.0.0 |
| Flutter | >=3.16.0 |
| Version | 0.0.1 |
| Channel | com.moontechnolabs.flutter_windows_iap |
| License | MIT |
Features
| Feature | Description |
|---|---|
| Product catalog | Load add-on metadata (title, description, price, kind) from the Microsoft Store by Store ID |
| Purchase flow | Trigger the system purchase UI via RequestPurchaseAsync |
| License checks | Verify whether the user owns a specific add-on or any active add-on |
| Add-on licenses | Read full license map (isActive, expiration, SKU, offer token) |
| One Store IDs | Resolve purchaseId, collectionId, and OAuth token for server-side validation |
| No bundled UI | Data APIs only — you own the widgets and paywall |
Table of contents
- Install
- Configure Microsoft Store
- Quick start
- Usage
- API reference
- Architecture
- Example app
- Limitations & notes
- License
Install
dependencies:
flutter_windows_iap: ^0.0.1
flutter pub add flutter_windows_iap
import 'package:flutter_windows_iap/flutter_windows_iap.dart';
Configure Microsoft Store
This plugin talks to the Microsoft Store at runtime. Your app must be MSIX-packaged and associated with a Store listing. Unpackaged debug builds will not receive valid Store responses.
1. Partner Center — create add-ons
- Open Partner Center → your app → Monetization → Add-ons.
- Create one or more add-ons (Consumable, Durable, or Unmanaged Consumable).
- Copy each add-on’s Store ID. You pass this string to every API that accepts a
storeId.
| Add-on type in Partner Center | productKind you may see |
Typical use |
|---|---|---|
| Consumable | Consumable |
Coins, credits, one-time use |
| Durable | Durable |
Permanent unlock, non-expiring subscription SKU |
| Unmanaged consumable | UnmanagedConsumable |
Consumables you track on your own server |
Subscription add-ons are often configured with ProductKind
Durablein the Store API. The plugin queriesConsumable,Durable, andUnmanagedConsumablewhen loading products.
2. Package identity
Your MSIX package identity (name, publisher, version) must match the app registered in Partner Center. Use tools such as msix or Visual Studio packaging to produce a build you can sideload or submit to the Store.
3. Azure AD / One Store (optional — for getPurchaseId)
Server-side purchase validation requires OAuth credentials from Azure AD:
| Field | Required | Why |
|---|---|---|
clientId |
Yes | Azure AD application (client) ID |
clientSecret |
Yes | Client secret for the app registration |
tenantId |
Yes | Azure AD tenant that owns the app |
resource |
No | OAuth resource for One Store B2B token (default: https://onestore.microsoft.com) |
collectionsResource |
No | OAuth resource for collections token (default: https://onestore.microsoft.com/b2b/keys/create/collections) |
Register your app in Azure Portal, grant it access to the Microsoft Store commerce APIs, and pass credentials via OneStoreConfig.
4. Testing
- Store sandbox: Use a Microsoft account enrolled in Partner Center’s sandbox testers.
- Sideloaded MSIX: Install a signed package whose identity matches Partner Center before calling purchase APIs.
- Logs: Native code writes debug logs to
%LocalAppData%\flutter_windows_iap_example\iap_log.txtwhen running the example app.
Quick start
import 'package:flutter/services.dart';
import 'package:flutter_windows_iap/flutter_windows_iap.dart';
Future<void> purchaseAddon() async {
final iap = FlutterWindowsIap();
// Store ID from Partner Center → Monetization → Add-ons
const storeId = 'StoreId';
// 1. Load product info (title, price, whether user already owns it)
final products = await iap.getProducts(storeId);
if (products.isEmpty) return;
// 2. Start purchase (opens Microsoft Store UI)
final status = await iap.makePurchase(storeId);
// 3. Verify entitlement
final owns = await iap.checkPurchase(storeId: storeId);
}
Usage
Create one instance of FlutterWindowsIap and reuse it for the app lifetime.
final iap = FlutterWindowsIap();
1. Get products (getProducts)
Loads Store metadata for a single add-on Store ID.
final List<Product> products = await iap.getProducts('StoreId');
| Parameter | Type | Required | What to pass | Why |
|---|---|---|---|---|
storeId |
String |
Yes | Add-on Store ID from Partner Center | Identifies the SKU in GetStoreProductsAsync |
Returns: Future<List<Product>> — usually one item when the ID is valid; empty list when nothing is found.
Throws: PlatformException when the Store returns an extended error (misconfigured product, network failure, app not associated with Store, etc.).
| Field | Type | Description |
|---|---|---|
title |
String? |
Localized product title from the Store |
description |
String? |
Localized description |
price |
String? |
Formatted price string (e.g. $4.99, $9.99 per month) |
inCollection |
bool? |
true if the signed-in user already owns this add-on |
productKind |
String? |
Store kind: Consumable, Durable, or UnmanagedConsumable |
storeId |
String? |
Echo of the Store ID |
Example — display in UI:
for (final p in products) {
print('${p.title} — ${p.price} (${p.productKind})');
if (p.inCollection == true) print('Already owned');
}
2. Make purchase (makePurchase)
Opens the Microsoft Store purchase dialog for the given add-on.
final StorePurchaseStatus? status = await iap.makePurchase('StoreId');
| Parameter | Type | Required | What to pass | Why |
|---|---|---|---|---|
storeId |
String |
Yes | Same Store ID used in getProducts |
Passed to RequestPurchaseAsync |
Returns: Future<StorePurchaseStatus?> — see enum below. Returns null for an unrecognized status code.
Throws: PlatformException when ExtendedError is not S_OK (e.g. product not configured — HRESULT 0x803f6107).
| Status | Meaning | Typical next step |
|---|---|---|
StorePurchaseStatus.succeeded |
User completed purchase | Unlock content; call checkPurchase |
StorePurchaseStatus.alreadyPurchased |
User already owns it | Treat as entitled |
StorePurchaseStatus.notPurchased |
User cancelled or did not buy | Keep paywall |
StorePurchaseStatus.networkError |
Network issue | Retry later |
StorePurchaseStatus.serverError |
Store server error | Retry later |
switch (await iap.makePurchase(storeId)) {
case StorePurchaseStatus.succeeded:
case StorePurchaseStatus.alreadyPurchased:
// grant access
break;
case StorePurchaseStatus.notPurchased:
// user declined
break;
case StorePurchaseStatus.networkError:
case StorePurchaseStatus.serverError:
// show error, retry
break;
default:
break;
}
3. Check purchase (checkPurchase)
Checks whether the current user has an active add-on license. Uses GetAppLicenseAsync and inspects add-on licenses.
final bool owns = await iap.checkPurchase(storeId: 'StoreId');
| Parameter | Type | Required | What to pass | Why |
|---|---|---|---|---|
storeId |
String |
No (default '') |
Specific add-on Store ID, or empty string | Controls which license is checked — see rules below |
Returns: Future<bool>
storeId value |
Returns true when… |
Returns false when… |
|---|---|---|
| Non-empty | That add-on’s license exists and IsActive == true |
ID not found, inactive, or app license inactive |
Empty ('') |
Any add-on license has IsActive == true |
No active add-ons, or app license inactive |
If the app license itself is not active (
AppLicense.IsActive == false), this method always returnsfalse.
Use this on app start, after purchase, and when restoring entitlements:
// Premium feature for one SKU
if (await iap.checkPurchase(storeId: premiumStoreId)) {
showPremiumContent();
}
// Any paid add-on (e.g. “has anything unlocked?”)
if (await iap.checkPurchase()) {
showPaidTier();
}
4. Add-on licenses (getAddonLicenses)
Returns the full license map for all add-ons the user has ever acquired.
final Map<String, StoreLicense> licenses = await iap.getAddonLicenses();
| Parameter | None |
Returns: Future<Map<String, StoreLicense>> — keys are add-on Store IDs; values are license objects.
StoreLicense field |
Type | Description |
|---|---|---|
isActive |
bool? |
Whether the license is currently valid |
skuStoreId |
String? |
SKU Store ID |
inAppOfferToken |
String? |
Offer token for promotions / trials |
expirationDate |
num? |
Raw Windows DateTime tick count |
Helper:
final expiry = license.getExpirationDate(); // DateTime? — converted from Store ticks
Use when you need expiration dates, offer tokens, or a full entitlement snapshot:
for (final entry in licenses.entries) {
print('${entry.key}: active=${entry.value.isActive}, '
'expires=${entry.value.getExpirationDate()}');
}
5. Purchase & collection IDs (getPurchaseId)
Resolves Microsoft Store customer identifiers for server-side purchase and collections validation (One Store commerce API).
final Map<String, String> ids = await iap.getPurchaseId(
'UserId',
config: const OneStoreConfig(
clientId: 'ClientId',
clientSecret: 'ClientSecret',
tenantId: 'TenantId',
),
);
| Parameter | Type | Required | What to pass | Why |
|---|---|---|---|---|
userId |
String |
Yes | A stable, anonymous ID you assign per user (not PII) | Passed to GetCustomerPurchaseIdAsync / GetCustomerCollectionsIdAsync |
config |
OneStoreConfig? |
Yes (credentials required) | Azure AD OAuth settings | Acquires tokens for Store API calls |
Returns: Future<Map<String, String>>
| Key | Description |
|---|---|
purchaseId |
Customer purchase ID for purchase validation APIs |
collectionId |
Customer collections ID for entitlement queries |
azureToken |
One Store B2B OAuth access token (use promptly; short-lived) |
If native code returns no map, Dart fills 'NA' for each key. On failure, expect PlatformException with codes such as invalid_oauth_config, oauth_token_failed, or oauth_collections_token_failed.
Security: Never ship clientSecret in client code for production. Prefer a backend that holds secrets and returns tokens or validated entitlements to the app.
// Send to your backend — do not log secrets or tokens in release builds
await http.post(
Uri.parse('https://your-api.com/validate-purchase'),
body: {
'userId': userId,
'purchaseId': ids['purchaseId']!,
'collectionId': ids['collectionId']!,
},
);
API reference
FlutterWindowsIap
| Method | Returns | Description |
|---|---|---|
getProducts(storeId) |
Future<List<Product>> |
Load add-on metadata from Store catalog |
makePurchase(storeId) |
Future<StorePurchaseStatus?> |
Start Store purchase UI |
checkPurchase({storeId}) |
Future<bool> |
Check active add-on license |
getAddonLicenses() |
Future<Map<String, StoreLicense>> |
All add-on licenses for current user |
getPurchaseId(userId, {config}) |
Future<Map<String, String>> |
Purchase ID, collection ID, OAuth token |
Limitations & notes
| Topic | Detail |
|---|---|
| Platform | Windows only. Other platforms are not supported by this package. |
| Packaging | Store APIs require an MSIX-packaged app associated with Partner Center. Plain flutter run without Store association may fail or return empty data. |
| Store ID | Must match an add-on configured and published (or in flight) in Partner Center for your app identity. |
| User account | Purchases and licenses are tied to the Microsoft account signed into the Store on the device. |
| Consumables | Microsoft does not auto-consume consumables; your app or backend must track consumption if needed. |
| Subscriptions | Renewal and grace periods follow Store rules; use StoreLicense.isActive and getExpirationDate() for time-bound access. |
| OAuth secrets | getPurchaseId needs Azure credentials — keep secrets on a server in production. |
| Errors | getProducts and makePurchase surface Store ExtendedError as PlatformException. Always wrap in try/catch in UI code. |
| macOS stub | getProducts / checkPurchase contain macOS stubs that throw or return false — ignore unless you fork the plugin. |
License
MIT © MOON TECHNOLABS PVT LTD — see LICENSE.