Skip to content

Commit 443a017

Browse files
committed
Added appAccountToken for ios
1 parent 7b8936d commit 443a017

File tree

4 files changed

+195
-8
lines changed

4 files changed

+195
-8
lines changed

guest-js/index.ts

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { invoke } from "@tauri-apps/api/core";
22
import { listen } from "@tauri-apps/api/event";
33

4+
/**
5+
* Response from IAP initialization
6+
*/
47
export interface InitializeResponse {
58
success: boolean;
69
}
710

11+
/**
12+
* Represents a pricing phase for subscription products
13+
*/
814
export interface PricingPhase {
915
formattedPrice: string;
1016
priceCurrencyCode: string;
@@ -14,13 +20,19 @@ export interface PricingPhase {
1420
recurrenceMode: number;
1521
}
1622

23+
/**
24+
* Subscription offer details including pricing phases
25+
*/
1726
export interface SubscriptionOffer {
1827
offerToken: string;
1928
basePlanId: string;
2029
offerId?: string;
2130
pricingPhases: PricingPhase[];
2231
}
2332

33+
/**
34+
* Product information from the app store
35+
*/
2436
export interface Product {
2537
productId: string;
2638
title: string;
@@ -32,10 +44,16 @@ export interface Product {
3244
subscriptionOfferDetails?: SubscriptionOffer[];
3345
}
3446

47+
/**
48+
* Response containing products fetched from the store
49+
*/
3550
export interface GetProductsResponse {
3651
products: Product[];
3752
}
3853

54+
/**
55+
* Purchase transaction information
56+
*/
3957
export interface Purchase {
4058
orderId?: string;
4159
packageName: string;
@@ -49,10 +67,16 @@ export interface Purchase {
4967
signature: string;
5068
}
5169

70+
/**
71+
* Response containing restored purchases
72+
*/
5273
export interface RestorePurchasesResponse {
5374
purchases: Purchase[];
5475
}
5576

77+
/**
78+
* Historical purchase record
79+
*/
5680
export interface PurchaseHistoryRecord {
5781
productId: string;
5882
purchaseTime: number;
@@ -62,20 +86,32 @@ export interface PurchaseHistoryRecord {
6286
signature: string;
6387
}
6488

89+
/**
90+
* Response containing purchase history
91+
*/
6592
export interface GetPurchaseHistoryResponse {
6693
history: PurchaseHistoryRecord[];
6794
}
6895

96+
/**
97+
* Response from acknowledging a purchase
98+
*/
6999
export interface AcknowledgePurchaseResponse {
70100
success: boolean;
71101
}
72102

103+
/**
104+
* Purchase state enumeration
105+
*/
73106
export enum PurchaseState {
74107
PURCHASED = 0,
75108
CANCELED = 1,
76109
PENDING = 2,
77110
}
78111

112+
/**
113+
* Current status of a product for the user
114+
*/
79115
export interface ProductStatus {
80116
productId: string;
81117
isOwned: boolean;
@@ -87,16 +123,51 @@ export interface ProductStatus {
87123
purchaseToken?: string;
88124
}
89125

126+
/**
127+
* Optional parameters for purchase requests
128+
*/
90129
export interface PurchaseOptions {
130+
/** Offer token for subscription products (Android) */
91131
offerToken?: string;
132+
/** Obfuscated account identifier for fraud prevention (Android only) */
92133
obfuscatedAccountId?: string;
134+
/** Obfuscated profile identifier for fraud prevention (Android only) */
93135
obfuscatedProfileId?: string;
136+
/** App account token - must be a valid UUID string (iOS only) */
137+
appAccountToken?: string;
94138
}
95139

140+
/**
141+
* Initialize the IAP plugin.
142+
* Must be called before any other IAP operations.
143+
*
144+
* @returns Promise resolving to initialization status
145+
* @example
146+
* ```typescript
147+
* const result = await initialize();
148+
* if (result.success) {
149+
* console.log('IAP initialized successfully');
150+
* }
151+
* ```
152+
*/
96153
export async function initialize(): Promise<InitializeResponse> {
97154
return await invoke<InitializeResponse>("plugin:iap|initialize");
98155
}
99156

157+
/**
158+
* Fetch product information from the app store.
159+
*
160+
* @param productIds - Array of product identifiers to fetch
161+
* @param productType - Type of products: "subs" for subscriptions, "inapp" for one-time purchases
162+
* @returns Promise resolving to product information
163+
* @example
164+
* ```typescript
165+
* const { products } = await getProducts(
166+
* ['com.example.premium', 'com.example.remove_ads'],
167+
* 'inapp'
168+
* );
169+
* ```
170+
*/
100171
export async function getProducts(
101172
productIds: string[],
102173
productType: "subs" | "inapp" = "subs",
@@ -109,6 +180,31 @@ export async function getProducts(
109180
});
110181
}
111182

183+
/**
184+
* Initiate a purchase for the specified product.
185+
*
186+
* @param productId - Product identifier to purchase
187+
* @param productType - Type of product: "subs" or "inapp"
188+
* @param options - Optional purchase parameters (platform-specific)
189+
* @returns Promise resolving to purchase transaction details
190+
* @example
191+
* ```typescript
192+
* // Simple purchase
193+
* const purchase = await purchase('com.example.premium', 'subs');
194+
*
195+
* // With options (iOS)
196+
* const purchase = await purchase('com.example.premium', 'subs', {
197+
* appAccountToken: '550e8400-e29b-41d4-a716-446655440000' // Must be valid UUID
198+
* });
199+
*
200+
* // With options (Android)
201+
* const purchase = await purchase('com.example.premium', 'subs', {
202+
* offerToken: 'offer_token_here',
203+
* obfuscatedAccountId: 'user_account_id',
204+
* obfuscatedProfileId: 'user_profile_id'
205+
* });
206+
* ```
207+
*/
112208
export async function purchase(
113209
productId: string,
114210
productType: "subs" | "inapp" = "subs",
@@ -123,6 +219,19 @@ export async function purchase(
123219
});
124220
}
125221

222+
/**
223+
* Restore user's previous purchases.
224+
*
225+
* @param productType - Type of products to restore: "subs" or "inapp"
226+
* @returns Promise resolving to list of restored purchases
227+
* @example
228+
* ```typescript
229+
* const { purchases } = await restorePurchases('subs');
230+
* purchases.forEach(purchase => {
231+
* console.log(`Restored: ${purchase.productId}`);
232+
* });
233+
* ```
234+
*/
126235
export async function restorePurchases(
127236
productType: "subs" | "inapp" = "subs",
128237
): Promise<RestorePurchasesResponse> {
@@ -136,12 +245,40 @@ export async function restorePurchases(
136245
);
137246
}
138247

248+
/**
249+
* Get the user's purchase history.
250+
* Note: Not supported on all platforms.
251+
*
252+
* @returns Promise resolving to purchase history
253+
* @example
254+
* ```typescript
255+
* const { history } = await getPurchaseHistory();
256+
* history.forEach(record => {
257+
* console.log(`Purchase: ${record.productId} at ${record.purchaseTime}`);
258+
* });
259+
* ```
260+
*/
139261
export async function getPurchaseHistory(): Promise<GetPurchaseHistoryResponse> {
140262
return await invoke<GetPurchaseHistoryResponse>(
141263
"plugin:iap|get_purchase_history",
142264
);
143265
}
144266

267+
/**
268+
* Acknowledge a purchase (Android only).
269+
* Purchases must be acknowledged within 3 days or they will be refunded.
270+
* iOS automatically acknowledges purchases.
271+
*
272+
* @param purchaseToken - Purchase token from the transaction
273+
* @returns Promise resolving to acknowledgment status
274+
* @example
275+
* ```typescript
276+
* const result = await acknowledgePurchase(purchase.purchaseToken);
277+
* if (result.success) {
278+
* console.log('Purchase acknowledged');
279+
* }
280+
* ```
281+
*/
145282
export async function acknowledgePurchase(
146283
purchaseToken: string,
147284
): Promise<AcknowledgePurchaseResponse> {
@@ -155,6 +292,24 @@ export async function acknowledgePurchase(
155292
);
156293
}
157294

295+
/**
296+
* Get the current status of a product for the user.
297+
* Checks if the product is owned, expired, or available for purchase.
298+
*
299+
* @param productId - Product identifier to check
300+
* @param productType - Type of product: "subs" or "inapp"
301+
* @returns Promise resolving to product status
302+
* @example
303+
* ```typescript
304+
* const status = await getProductStatus('com.example.premium', 'subs');
305+
* if (status.isOwned) {
306+
* console.log('User owns this product');
307+
* if (status.isAutoRenewing) {
308+
* console.log('Subscription is auto-renewing');
309+
* }
310+
* }
311+
* ```
312+
*/
158313
export async function getProductStatus(
159314
productId: string,
160315
productType: "subs" | "inapp" = "subs",
@@ -167,7 +322,25 @@ export async function getProductStatus(
167322
});
168323
}
169324

170-
// Event listener for purchase updates
325+
/**
326+
* Listen for purchase updates.
327+
* This event is triggered when a purchase state changes.
328+
*
329+
* @param callback - Function to call when a purchase is updated
330+
* @returns Cleanup function to stop listening
331+
* @example
332+
* ```typescript
333+
* const unsubscribe = onPurchaseUpdated((purchase) => {
334+
* console.log(`Purchase updated: ${purchase.productId}`);
335+
* if (purchase.purchaseState === PurchaseState.PURCHASED) {
336+
* // Handle successful purchase
337+
* }
338+
* });
339+
*
340+
* // Later, stop listening
341+
* unsubscribe();
342+
* ```
343+
*/
171344
export function onPurchaseUpdated(
172345
callback: (purchase: Purchase) => void,
173346
): () => void {

ios/Sources/IapPlugin.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class PurchaseArgs: Decodable {
1414
let productId: String
1515
let productType: String?
1616
let offerToken: String?
17+
let appAccountToken: String?
1718
}
1819

1920
class RestorePurchasesArgs: Decodable {
@@ -157,8 +158,22 @@ class IapPlugin: Plugin {
157158
return
158159
}
159160

160-
// Initiate purchase
161-
let result = try await product.purchase()
161+
// Prepare purchase options
162+
var purchaseOptions: Set<Product.PurchaseOption> = []
163+
164+
// Add appAccountToken if provided (must be a valid UUID)
165+
if let appAccountToken = args.appAccountToken {
166+
guard let uuid = UUID(uuidString: appAccountToken) else {
167+
invoke.reject("Invalid appAccountToken: must be a valid UUID string")
168+
return
169+
}
170+
purchaseOptions.insert(.appAccountToken(uuid))
171+
}
172+
173+
// Initiate purchase with options
174+
let result = purchaseOptions.isEmpty
175+
? try await product.purchase()
176+
: try await product.purchase(options: purchaseOptions)
162177

163178
switch result {
164179
case .success(let verification):

src/commands.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ pub(crate) async fn purchase<R: Runtime>(
2222
app: AppHandle<R>,
2323
payload: PurchaseRequest,
2424
) -> Result<Purchase> {
25-
app.iap().purchase(
26-
payload.product_id,
27-
payload.product_type,
28-
payload.options,
29-
)
25+
app.iap()
26+
.purchase(payload.product_id, payload.product_type, payload.options)
3027
}
3128

3229
#[command]

src/models.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub struct PurchaseOptions {
7474
pub obfuscated_account_id: Option<String>,
7575
#[serde(skip_serializing_if = "Option::is_none")]
7676
pub obfuscated_profile_id: Option<String>,
77+
#[serde(skip_serializing_if = "Option::is_none")]
78+
pub app_account_token: Option<String>,
7779
}
7880

7981
#[derive(Debug, Deserialize, Serialize)]

0 commit comments

Comments
 (0)