mirror of
https://github.com/readest/readest.git
synced 2026-04-28 19:42:21 +00:00
feat: add IAP in native bridge plugin for iOS (#1673)
This commit is contained in:
parent
beaf034035
commit
f3e9983742
16 changed files with 718 additions and 2 deletions
|
|
@ -9,6 +9,10 @@ const COMMANDS: &[&str] = &[
|
|||
"get_sys_fonts_list",
|
||||
"intercept_keys",
|
||||
"lock_screen_orientation",
|
||||
"iap_initialize",
|
||||
"iap_fetch_products",
|
||||
"iap_purchase_product",
|
||||
"iap_restore_purchases",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import AuthenticationServices
|
|||
import CoreText
|
||||
import MediaPlayer
|
||||
import ObjectiveC
|
||||
import StoreKit
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
|
|
@ -48,6 +49,37 @@ class LockScreenOrientationRequestArgs: Decodable {
|
|||
let orientation: String?
|
||||
}
|
||||
|
||||
struct InitializeRequest: Decodable {
|
||||
let publicKey: String?
|
||||
}
|
||||
|
||||
struct FetchProductsRequest: Decodable {
|
||||
let productIds: [String]
|
||||
}
|
||||
|
||||
struct PurchaseProductRequest: Decodable {
|
||||
let productId: String
|
||||
}
|
||||
|
||||
struct ProductData: Codable {
|
||||
let id: String
|
||||
let title: String
|
||||
let description: String
|
||||
let price: String
|
||||
let priceCurrencyCode: String?
|
||||
let priceAmountMicros: Int64
|
||||
let productType: String
|
||||
}
|
||||
|
||||
struct PurchaseData: Codable {
|
||||
let productId: String
|
||||
let transactionId: String
|
||||
let originalTransactionId: String
|
||||
let purchaseDate: String
|
||||
let purchaseState: String
|
||||
let platform: String
|
||||
}
|
||||
|
||||
class VolumeKeyHandler: NSObject {
|
||||
private var audioSession: AVAudioSession?
|
||||
private var originalVolume: Float = 0.0
|
||||
|
|
@ -422,6 +454,85 @@ class NativeBridgePlugin: Plugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func iap_initialize(_ invoke: Invoke) {
|
||||
StoreKitManager.shared.initialize()
|
||||
invoke.resolve(["success": true])
|
||||
}
|
||||
|
||||
@objc public func iap_fetch_products(_ invoke: Invoke) {
|
||||
do {
|
||||
let args = try invoke.parseArgs(FetchProductsRequest.self)
|
||||
|
||||
StoreKitManager.shared.fetchProducts(productIds: args.productIds) { products in
|
||||
let productsData: [ProductData] = products.map { product in
|
||||
return ProductData(
|
||||
id: product.productIdentifier,
|
||||
title: product.localizedTitle,
|
||||
description: product.localizedDescription,
|
||||
price: product.price.stringValue,
|
||||
priceCurrencyCode: product.priceLocale.currencyCode,
|
||||
priceAmountMicros: Int64(product.price.doubleValue * 1_000_000),
|
||||
productType: product.productIdentifier.contains("monthly")
|
||||
|| product.productIdentifier.contains("yearly") ? "subscription" : "consumable"
|
||||
)
|
||||
}
|
||||
invoke.resolve(["products": productsData])
|
||||
}
|
||||
} catch {
|
||||
invoke.reject("Failed to parse fetch products arguments: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func iap_purchase_product(_ invoke: Invoke) {
|
||||
do {
|
||||
let args = try invoke.parseArgs(PurchaseProductRequest.self)
|
||||
|
||||
StoreKitManager.shared.fetchProducts(productIds: [args.productId]) { products in
|
||||
guard let product = products.first else {
|
||||
invoke.reject("Product not found")
|
||||
return
|
||||
}
|
||||
|
||||
StoreKitManager.shared.purchase(product: product) { result in
|
||||
switch result {
|
||||
case .success(let txn):
|
||||
let purchase = PurchaseData(
|
||||
productId: txn.payment.productIdentifier,
|
||||
transactionId: txn.transactionIdentifier ?? "",
|
||||
originalTransactionId: txn.original?.transactionIdentifier ?? txn
|
||||
.transactionIdentifier ?? "",
|
||||
purchaseDate: ISO8601DateFormatter().string(from: txn.transactionDate ?? Date()),
|
||||
purchaseState: "purchased",
|
||||
platform: "ios"
|
||||
)
|
||||
invoke.resolve(["purchase": purchase])
|
||||
case .failure(let error):
|
||||
invoke.reject("Purchase failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
invoke.reject("Failed to parse purchase arguments: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func iap_restore_purchases(_ invoke: Invoke) {
|
||||
StoreKitManager.shared.restorePurchases { transactions in
|
||||
let restored = transactions.map { txn -> PurchaseData in
|
||||
return PurchaseData(
|
||||
productId: txn.payment.productIdentifier,
|
||||
transactionId: txn.transactionIdentifier ?? "",
|
||||
originalTransactionId: txn.original?.transactionIdentifier ?? txn.transactionIdentifier
|
||||
?? "",
|
||||
purchaseDate: ISO8601DateFormatter().string(from: txn.transactionDate ?? Date()),
|
||||
purchaseState: "restored",
|
||||
platform: "ios"
|
||||
)
|
||||
}
|
||||
invoke.resolve(["purchases": restored])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_native_bridge")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
import StoreKit
|
||||
import os
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "StoreKitManager")
|
||||
|
||||
class StoreKitManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
|
||||
static let shared = StoreKitManager()
|
||||
|
||||
private var productsRequest: SKProductsRequest?
|
||||
private var productResponseHandler: (([SKProduct]) -> Void)?
|
||||
private var purchaseHandler: ((Result<SKPaymentTransaction, Error>) -> Void)?
|
||||
private var restoreHandler: (([SKPaymentTransaction]) -> Void)?
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
SKPaymentQueue.default().add(self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
SKPaymentQueue.default().remove(self)
|
||||
}
|
||||
|
||||
func fetchProducts(productIds: [String], completion: @escaping ([SKProduct]) -> Void) {
|
||||
let ids = Set(productIds)
|
||||
productsRequest = SKProductsRequest(productIdentifiers: ids)
|
||||
productsRequest?.delegate = self
|
||||
productResponseHandler = completion
|
||||
productsRequest?.start()
|
||||
}
|
||||
|
||||
func purchase(
|
||||
product: SKProduct, completion: @escaping (Result<SKPaymentTransaction, Error>) -> Void
|
||||
) {
|
||||
guard SKPaymentQueue.canMakePayments() else {
|
||||
completion(
|
||||
.failure(
|
||||
NSError(
|
||||
domain: "iap", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchases disabled."])))
|
||||
return
|
||||
}
|
||||
purchaseHandler = completion
|
||||
let payment = SKPayment(product: product)
|
||||
SKPaymentQueue.default().add(payment)
|
||||
}
|
||||
|
||||
func restorePurchases(completion: @escaping ([SKPaymentTransaction]) -> Void) {
|
||||
restoreHandler = completion
|
||||
SKPaymentQueue.default().restoreCompletedTransactions()
|
||||
}
|
||||
|
||||
// MARK: - SKProductsRequestDelegate
|
||||
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.productResponseHandler?(response.products)
|
||||
self?.productResponseHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ request: SKRequest, didFailWithError error: Error) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
logger.error("Products request failed: \(error.localizedDescription)")
|
||||
self?.productResponseHandler?([])
|
||||
self?.productResponseHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SKPaymentTransactionObserver
|
||||
func paymentQueue(
|
||||
_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]
|
||||
) {
|
||||
for transaction in transactions {
|
||||
switch transaction.transactionState {
|
||||
case .purchased:
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.purchaseHandler?(.success(transaction))
|
||||
self?.purchaseHandler = nil
|
||||
}
|
||||
case .failed:
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
let error =
|
||||
transaction.error
|
||||
?? NSError(
|
||||
domain: "iap", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unknown purchase error"]
|
||||
)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.purchaseHandler?(.failure(error))
|
||||
self?.purchaseHandler = nil
|
||||
}
|
||||
case .restored:
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
case .deferred, .purchasing:
|
||||
// Handle these states if needed
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||||
let restored = queue.transactions.filter { $0.transactionState == .restored }
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.restoreHandler?(restored)
|
||||
self?.restoreHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
func paymentQueue(
|
||||
_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error
|
||||
) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
logger.error("Restore purchases failed: \(error.localizedDescription)")
|
||||
self?.restoreHandler?([])
|
||||
self?.restoreHandler = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-iap-fetch-products"
|
||||
description = "Enables the iap_fetch_products command without any pre-configured scope."
|
||||
commands.allow = ["iap_fetch_products"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-iap-fetch-products"
|
||||
description = "Denies the iap_fetch_products command without any pre-configured scope."
|
||||
commands.deny = ["iap_fetch_products"]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-iap-initialize"
|
||||
description = "Enables the iap_initialize command without any pre-configured scope."
|
||||
commands.allow = ["iap_initialize"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-iap-initialize"
|
||||
description = "Denies the iap_initialize command without any pre-configured scope."
|
||||
commands.deny = ["iap_initialize"]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-iap-purchase-product"
|
||||
description = "Enables the iap_purchase_product command without any pre-configured scope."
|
||||
commands.allow = ["iap_purchase_product"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-iap-purchase-product"
|
||||
description = "Denies the iap_purchase_product command without any pre-configured scope."
|
||||
commands.deny = ["iap_purchase_product"]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-iap-restore-purchases"
|
||||
description = "Enables the iap_restore_purchases command without any pre-configured scope."
|
||||
commands.allow = ["iap_restore_purchases"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-iap-restore-purchases"
|
||||
description = "Denies the iap_restore_purchases command without any pre-configured scope."
|
||||
commands.deny = ["iap_restore_purchases"]
|
||||
|
|
@ -14,6 +14,10 @@ Default permissions for the plugin
|
|||
- `allow-get-sys-fonts-list`
|
||||
- `allow-intercept-keys`
|
||||
- `allow-lock-screen-orientation`
|
||||
- `allow-iap-initialize`
|
||||
- `allow-iap-fetch-products`
|
||||
- `allow-iap-purchase-product`
|
||||
- `allow-iap-restore-purchases`
|
||||
|
||||
## Permission Table
|
||||
|
||||
|
|
@ -157,6 +161,110 @@ Denies the get_sys_fonts_list command without any pre-configured scope.
|
|||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-fetch-products`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the iap_fetch_products command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-iap-fetch-products`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the iap_fetch_products command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-initialize`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the iap_initialize command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-iap-initialize`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the iap_initialize command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-purchase-product`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the iap_purchase_product command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-iap-purchase-product`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the iap_purchase_product command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-iap-restore-purchases`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the iap_restore_purchases command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-iap-restore-purchases`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the iap_restore_purchases command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-install-package`
|
||||
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -11,4 +11,8 @@ permissions = [
|
|||
"allow-get-sys-fonts-list",
|
||||
"allow-intercept-keys",
|
||||
"allow-lock-screen-orientation",
|
||||
"allow-iap-initialize",
|
||||
"allow-iap-fetch-products",
|
||||
"allow-iap-purchase-product",
|
||||
"allow-iap-restore-purchases",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -354,6 +354,54 @@
|
|||
"const": "deny-get-sys-fonts-list",
|
||||
"markdownDescription": "Denies the get_sys_fonts_list command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_fetch_products command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-iap-fetch-products",
|
||||
"markdownDescription": "Enables the iap_fetch_products command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the iap_fetch_products command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-iap-fetch-products",
|
||||
"markdownDescription": "Denies the iap_fetch_products command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_initialize command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-iap-initialize",
|
||||
"markdownDescription": "Enables the iap_initialize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the iap_initialize command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-iap-initialize",
|
||||
"markdownDescription": "Denies the iap_initialize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_purchase_product command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-iap-purchase-product",
|
||||
"markdownDescription": "Enables the iap_purchase_product command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the iap_purchase_product command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-iap-purchase-product",
|
||||
"markdownDescription": "Denies the iap_purchase_product command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the iap_restore_purchases command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-iap-restore-purchases",
|
||||
"markdownDescription": "Enables the iap_restore_purchases command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the iap_restore_purchases command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-iap-restore-purchases",
|
||||
"markdownDescription": "Denies the iap_restore_purchases command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install_package command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
|
@ -415,10 +463,10 @@
|
|||
"markdownDescription": "Denies the use_background_audio command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`",
|
||||
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`"
|
||||
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,3 +81,34 @@ pub(crate) async fn lock_screen_orientation<R: Runtime>(
|
|||
) -> Result<()> {
|
||||
app.native_bridge().lock_screen_orientation(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_initialize<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: IAPInitializeRequest,
|
||||
) -> Result<IAPInitializeResponse> {
|
||||
app.native_bridge().iap_initialize(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_fetch_products<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: IAPFetchProductsRequest,
|
||||
) -> Result<IAPFetchProductsResponse> {
|
||||
app.native_bridge().iap_fetch_products(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_purchase_product<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: IAPPurchaseProductRequest,
|
||||
) -> Result<IAPPurchaseProductResponse> {
|
||||
app.native_bridge().iap_purchase_product(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn iap_restore_purchases<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<IAPRestorePurchasesResponse> {
|
||||
app.native_bridge().iap_restore_purchases()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,4 +73,29 @@ impl<R: Runtime> NativeBridge<R> {
|
|||
) -> crate::Result<()> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_initialize(
|
||||
&self,
|
||||
_payload: IAPInitializeRequest,
|
||||
) -> crate::Result<IAPInitializeResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_fetch_products(
|
||||
&self,
|
||||
_payload: IAPFetchProductsRequest,
|
||||
) -> crate::Result<IAPFetchProductsResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_purchase_product(
|
||||
&self,
|
||||
_payload: IAPPurchaseProductRequest,
|
||||
) -> crate::Result<IAPPurchaseProductResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn iap_restore_purchases(&self) -> crate::Result<IAPRestorePurchasesResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||
commands::get_sys_fonts_list,
|
||||
commands::intercept_keys,
|
||||
commands::lock_screen_orientation,
|
||||
commands::iap_initialize,
|
||||
commands::iap_fetch_products,
|
||||
commands::iap_purchase_product,
|
||||
commands::iap_restore_purchases,
|
||||
])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
|
|
|
|||
|
|
@ -112,3 +112,46 @@ impl<R: Runtime> NativeBridge<R> {
|
|||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_initialize(
|
||||
&self,
|
||||
payload: IAPInitializeRequest,
|
||||
) -> crate::Result<IAPInitializeResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("iap_initialize", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_fetch_products(
|
||||
&self,
|
||||
payload: IAPFetchProductsRequest,
|
||||
) -> crate::Result<IAPFetchProductsResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("iap_fetch_products", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_purchase_product(
|
||||
&self,
|
||||
payload: IAPPurchaseProductRequest,
|
||||
) -> crate::Result<IAPPurchaseProductResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("iap_purchase_product", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn iap_restore_purchases(
|
||||
&self,
|
||||
) -> crate::Result<IAPRestorePurchasesResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("iap_restore_purchases", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,3 +86,69 @@ pub struct InterceptKeysRequest {
|
|||
pub struct LockScreenOrientationRequest {
|
||||
pub orientation: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Product {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub price: String,
|
||||
pub price_currency_code: Option<String>,
|
||||
pub price_amount_micros: i64,
|
||||
pub product_type: String, // "consumable", "non_consumable", or "subscription"
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Purchase {
|
||||
pub product_id: String,
|
||||
pub transaction_id: String,
|
||||
pub purchase_date: String,
|
||||
pub original_transaction_id: String,
|
||||
pub purchase_state: String, // "purchased", "pending", "cancelled"
|
||||
pub platform: String, // "ios" or "android"
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPInitializeRequest {
|
||||
pub public_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPInitializeResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPFetchProductsRequest {
|
||||
pub product_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPFetchProductsResponse {
|
||||
pub products: Vec<Product>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPPurchaseProductRequest {
|
||||
pub product_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPPurchaseProductResponse {
|
||||
pub purchase: Option<Purchase>,
|
||||
pub cancelled_purchase: Option<Purchase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IAPRestorePurchasesResponse {
|
||||
pub purchases: Vec<Purchase>,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue