Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: manage subscriptions modal #532

Closed
wants to merge 10 commits into from
12 changes: 8 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ func resolveTargets() -> [Target] {
let infoPlist = "Purchases/Info.plist"

let baseTargets: [Target] = [
.target(name: "PurchasesSwift",
dependencies: ["PurchasesSwift"],
path: ".",
sources: ["PurchasesSwift"]),
.target(name: "Purchases",
dependencies: ["PurchasesCoreSwift"],
path: ".",
exclude: [infoPlist],
sources: ["Purchases"],
publicHeadersPath: "Purchases/Public",
cSettings: objcSources.map { CSetting.headerSearchPath($0) }
),
cSettings: objcSources.map { CSetting.headerSearchPath($0) }),
.target(name: "PurchasesCoreSwift",
dependencies: [],
path: ".",
sources: ["PurchasesCoreSwift"])]
sources: ["PurchasesCoreSwift"])
]

return baseTargets
}
Expand All @@ -43,7 +47,7 @@ let package = Package(
],
products: [
.library(name: "Purchases",
targets: ["Purchases"]),
targets: ["PurchasesSwift"]),
],
dependencies: [],
targets: resolveTargets()
Expand Down
358 changes: 357 additions & 1 deletion Purchases.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions Purchases.xcodeproj/xcshareddata/xcschemes/StoreKitTests.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@
ReferencedContainer = "container:Purchases.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D488A85267A724F00EFF50A"
BuildableName = "PurchasesSwiftTests.xctest"
BlueprintName = "PurchasesSwiftTests"
ReferencedContainer = "container:Purchases.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down Expand Up @@ -70,15 +80,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2DE20B7E26409EB7004C597D"
BuildableName = "StoreKitTestApp.app"
BlueprintName = "StoreKitTestApp"
ReferencedContainer = "container:Purchases.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
29 changes: 29 additions & 0 deletions PurchasesSwift.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Pod::Spec.new do |s|
s.name = "PurchasesSwift"
s.version = "3.12.0-SNAPSHOT"
s.summary = "Subscription and in-app-purchase backend service."

s.description = <<-DESC
Save yourself the hastle of implementing a subscriptions backend. Use RevenueCat instead https://www.revenuecat.com/
DESC

s.homepage = "https://www.revenuecat.com/"
s.license = { :type => 'MIT' }
s.author = { "RevenueCat, Inc." => "support@revenuecat.com" }
s.source = { :git => "https://github.com/revenuecat/purchases-ios.git", :tag => s.version.to_s }
s.documentation_url = "https://docs.revenuecat.com/"

s.framework = 'StoreKit'
s.swift_version = '5.0'

s.ios.deployment_target = '9.0'
s.osx.deployment_target = '10.12'
s.watchos.deployment_target = '6.2'
s.tvos.deployment_target = '9.0'

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.dependency 'Purchases', '3.12.0-SNAPSHOT'

s.source_files = ['PurchasesSwift/**/*.{swift}']

end
14 changes: 14 additions & 0 deletions PurchasesSwift/PurchasesSwift.docc/PurchasesSwift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ``PurchasesSwift``

<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->

## Overview

<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->

## Topics

### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->

- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->

19 changes: 19 additions & 0 deletions PurchasesSwift/PurchasesSwift.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// PurchasesSwift.h
// PurchasesSwift
//
// Created by Andrés Boedo on 6/16/21.
// Copyright © 2021 Purchases. All rights reserved.
//

#import <Foundation/Foundation.h>

//! Project version number for PurchasesSwift.
FOUNDATION_EXPORT double PurchasesSwiftVersionNumber;

//! Project version string for PurchasesSwift.
FOUNDATION_EXPORT const unsigned char PurchasesSwiftVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <PurchasesSwift/PublicHeader.h>


114 changes: 114 additions & 0 deletions PurchasesSwift/PurchasesSwift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// PurchasesSwift.swift
// PurchasesSwift
//
// Created by Andrés Boedo on 6/16/21.
// Copyright © 2021 Purchases. All rights reserved.
//

import Foundation
import StoreKit

@_exported import Purchases


@objc public extension Purchases {
@objc func showManageSubscriptionModal() {

self.purchaserInfo { purchaserInfo, error in
if let error = error {
print("there was an error getting purchaserInfo: \(error.localizedDescription)")
return
}

guard let purchaserInfo = purchaserInfo else {
print("there was no error but purchaserInfo is null!")
return
}

guard let managementURL = purchaserInfo.managementURL else {
print("managementURL is nil, opening iOS subscription management page")
self.showAppleManageSubscriptions()
return
}

#if os(iOS)
if managementURL.isAppleSubscription() {
if #available(iOS 15.0, *) {
detach {
await self.showSK2ManageSubscriptions()
}
}
return
}
#endif
self.openURL(managementURL)
}
}
}

public extension Purchases {

@available(iOS 9.0, *)
@available(macOS 10.12, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
func showAppleManageSubscriptions() {
#if os(iOS)
if #available(iOS 15.0, *) {
detach {
await self.showSK2ManageSubscriptions()
}
} else {
self.openURL(.appleSubscriptionsURL)
}
#elseif os(macOS)
self.openURL(.appleSubscriptionsURL)
#endif
}

@MainActor
@available(iOS 15.0, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it potentially be possible to batch these into a single custom annotation? i can see us repeating these lines alllll over the place

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'd be able to do it at the extension declaration level, and it'd apply to everything in it.

@available(macOS, unavailable)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
func showSK2ManageSubscriptions() async {
#if os(iOS)
let windowScene = UIApplication.shared
.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first

if let windowScene = windowScene as? UIWindowScene {

do {
try await AppStore.showManageSubscriptions(in: windowScene)
} catch let error {
print("error when trying to show manage subscription: \(error.localizedDescription)")
}
} else {
print("couldn't get window")
}
#endif
}

func openURL(_ url: URL) {
#if os(iOS)
if #available(iOS 10.0, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
#elseif os(macOS)
NSWorkspace.shared.open(url)
#endif
}
}

private extension URL {
func isAppleSubscription() -> Bool {
self.absoluteString.contains("apps.apple.com")
}

static let appleSubscriptionsURL = URL(string: "https://apps.apple.com/account/subscriptions")!
}
34 changes: 34 additions & 0 deletions PurchasesSwiftTests/PurchasesSwiftTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// PurchasesSwiftTests.swift
// PurchasesSwiftTests
//
// Created by Andrés Boedo on 6/16/21.
// Copyright © 2021 Purchases. All rights reserved.
//

import XCTest
@testable import PurchasesSwift

class PurchasesSwiftTests: XCTestCase {

override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}

func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}

}