Skip to content

Commit

Permalink
initial support for web2web (#122)
Browse files Browse the repository at this point in the history
* initial support for web2web
* fixed bug with consumable purchases
* improve transaction finish
  • Loading branch information
ren6 authored Oct 23, 2024
1 parent f231c43 commit 5e27d38
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 12 deletions.
2 changes: 1 addition & 1 deletion ApphudSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'ApphudSDK'
s.version = '3.5.3'
s.version = '3.5.5'
s.summary = 'Build and Measure In-App Subscriptions on iOS.'
s.description = 'Apphud covers every aspect when it comes to In-App Subscriptions from integration to analytics on iOS and Android.'
s.homepage = 'https://github.com/apphud/ApphudSDK'
Expand Down
18 changes: 13 additions & 5 deletions Sources/Internal/ApphudAsyncStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ internal class ApphudAsyncStoreKit {
break
}

if let tr = transaction {
_ = await ApphudInternal.shared.handleTransaction(tr)
await tr.finish()
if let transaction {
await Self.processTransaction(transaction)
}

self.isPurchasing = false
Expand All @@ -132,6 +131,16 @@ internal class ApphudAsyncStoreKit {
}
}

fileprivate static func processTransaction(_ transaction: StoreKit.Transaction) async {
_ = await ApphudInternal.shared.handleTransaction(transaction)
Task {
if (transaction.productType == .consumable) {
try? await Task.sleep(nanoseconds: 3_000_000_000)
}
await transaction.finish()
}
}

#if os(iOS) || os(tvOS) || os(macOS) || os(watchOS)
@MainActor
func purchase(product: Product, apphudProduct: ApphudProduct?, isPurchasing: Binding<Bool>? = nil) async -> ApphudAsyncPurchaseResult {
Expand Down Expand Up @@ -181,8 +190,7 @@ final class ApphudAsyncTransactionObserver {
return
}

_ = await ApphudInternal.shared.handleTransaction(transaction)
await transaction.finish()
await ApphudAsyncStoreKit.processTransaction(transaction)
}
} else {
apphudLog("Received transaction [\(transaction.id), \(transaction.productID)] from StoreKit2")
Expand Down
29 changes: 29 additions & 0 deletions Sources/Internal/ApphudInternal+Attribution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,33 @@ extension ApphudInternal {
return ["token": appleAttibutionToken]
}
}

@MainActor
internal func tryWebAttribution(attributionData: [AnyHashable: Any], completion: @escaping (Bool, ApphudUser?) -> Void) {
let userId = attributionData["aph_user_id"] ?? attributionData["apphud_user_id"]
if let userId = userId as? String, !userId.isEmpty {

if (currentUser?.userId == userId) {
apphudLog("Already web2web user, skipping")
completion(true, currentUser)
return
}

apphudLog("Found a match from web click, updating User ID to \(userId)", forceDisplay: true)
self.updateUser(fields: ["user_id": userId, "from_web2web": true]) { (result, _, data, _, _, _, attempts) in
if result {
Task {
await self.parseUser(data: data)
Task { @MainActor in
completion(true, self.currentUser)
}
}
} else {
completion(false, self.currentUser)
}
}
} else {
completion(false, currentUser)
}
}
}
6 changes: 3 additions & 3 deletions Sources/Internal/ApphudInternal+Purchase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ extension ApphudInternal {
transactionProductIdentifier: productID,
transactionState: isRecentlyPurchased ? .purchased : nil,
receiptString: receipt,
notifyDelegate: true) { _ in }
notifyDelegate: true) { _ in
continuation.resume(returning: true)
}
}

continuation.resume(returning: true)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/Internal/ApphudInternal+UserUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ extension ApphudInternal {
}

@MainActor
private func updateUser(fields: [String: Any], delay: Double = 0, callback: @escaping ApphudHTTPResponseCallback) {
internal func updateUser(fields: [String: Any], delay: Double = 0, callback: @escaping ApphudHTTPResponseCallback) {

// Requires @MainActor since it collects data from UIDevice
#if os(macOS)
Expand Down Expand Up @@ -338,6 +338,7 @@ extension ApphudInternal {
Task {
let values = await self.preparePropertiesParams(isAudience: force)
guard let params = values.0, let properties = values.1 else {
completion?(false)
return
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/Internal/ApphudStoreKitWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,10 @@ internal class ApphudStoreKitWrapper: NSObject, SKPaymentTransactionObserver, SK
internal func finishTransaction(_ transaction: SKPaymentTransaction) {
apphudLog("Finish Transaction: \(transaction.payment.productIdentifier), state: \(transaction.transactionState.rawValue), id: \(transaction.transactionIdentifier ?? "")")
NotificationCenter.default.post(name: _ApphudWillFinishTransactionNotification, object: transaction)
SKPaymentQueue.default().finishTransaction(transaction)

if (transaction.transactionState != .purchasing) {
SKPaymentQueue.default().finishTransaction(transaction)
}
self.purchasingProductID = nil
self.purchasingValue = nil
}
Expand Down
21 changes: 20 additions & 1 deletion Sources/Public/Apphud.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
import UserNotifications
import SwiftUI

internal let apphud_sdk_version = "3.5.3"
internal let apphud_sdk_version = "3.5.5"

// MARK: - Initialization

Expand Down Expand Up @@ -788,6 +788,25 @@ final public class Apphud: NSObject {
@objc public static func addAttribution(data: [AnyHashable: Any]?, from provider: ApphudAttributionProvider, identifer: String? = nil, callback: ApphudBoolCallback?) {
ApphudInternal.shared.addAttribution(rawData: data, from: provider, identifer: identifer, callback: callback)
}

/**
Web-to-Web flow only. Attempts to attribute the user with the provided attribution data.
If the `data` parameter contains either `aph_user_id` or `apphud_user_id`, the SDK will submit this information to the Apphud server.
The server will return a premium web user if found; otherwise, the callback will return `false`.
Additionally, the delegate methods `apphudSubscriptionsUpdated` and `apphudDidChangeUserID` will be called.
The callback returns `true` if the user is successfully attributed via the web and includes the updated `ApphudUser` object.
After this callback, you can check the `Apphud.hasPremiumAccess()` method, which should return `true` if the user has premium access.
- Parameters:
- data: A dictionary containing the attribution data.
- callback: A closure that returns a boolean indicating whether the web attribution was successful, and the updated `ApphudUser` object.
*/
@MainActor
public static func attributeFromWeb(data: [AnyHashable: Any], callback: @escaping (Bool, ApphudUser?) -> Void) {
ApphudInternal.shared.tryWebAttribution(attributionData: data, completion: callback)
}

// MARK: - Eligibility Checks

Expand Down

0 comments on commit 5e27d38

Please sign in to comment.