diff --git a/!examples/InAppPurchaseManager b/!examples/InAppPurchaseManager deleted file mode 160000 index 393d342..0000000 --- a/!examples/InAppPurchaseManager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 393d34253a1dbbb5f59a3cb2b863674c754d8e38 diff --git a/examples/InAppPurchaseManager.zip b/examples/InAppPurchaseManager.zip new file mode 100644 index 0000000..99aa88b Binary files /dev/null and b/examples/InAppPurchaseManager.zip differ diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.pbxproj b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8c2c9f2 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.pbxproj @@ -0,0 +1,386 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 7D4C0A642C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */; }; + 7D4C0A662C3ED21B0050CB10 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */; }; + 7D4C0A682C3ED21E0050CB10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */; }; + 7D4C0A6C2C3ED21E0050CB10 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */; }; + 7D4C0A742C3ED2B70050CB10 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */; }; + 7D4C0A762C3ED3570050CB10 /* store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A752C3ED3570050CB10 /* store.swift */; }; + 7D4C0A792C3EDEB70050CB10 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InAppPurchaseManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseManagerApp.swift; sourceTree = ""; }; + 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 7D4C0A692C3ED21E0050CB10 /* InAppPurchaseManager.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = InAppPurchaseManager.entitlements; sourceTree = ""; }; + 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 7D4C0A752C3ED3570050CB10 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = ""; }; + 7D4C0A772C3ED5C20050CB10 /* Product.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Product.storekit; sourceTree = ""; }; + 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7D4C0A5D2C3ED21B0050CB10 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D4C0A742C3ED2B70050CB10 /* StoreKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7D4C0A572C3ED21B0050CB10 = { + isa = PBXGroup; + children = ( + 7D4C0A772C3ED5C20050CB10 /* Product.storekit */, + 7D4C0A622C3ED21B0050CB10 /* InAppPurchaseManager */, + 7D4C0A612C3ED21B0050CB10 /* Products */, + 7D4C0A722C3ED2B70050CB10 /* Frameworks */, + ); + sourceTree = ""; + }; + 7D4C0A612C3ED21B0050CB10 /* Products */ = { + isa = PBXGroup; + children = ( + 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */, + ); + name = Products; + sourceTree = ""; + }; + 7D4C0A622C3ED21B0050CB10 /* InAppPurchaseManager */ = { + isa = PBXGroup; + children = ( + 7D4C0A752C3ED3570050CB10 /* store.swift */, + 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */, + 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */, + 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */, + 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */, + 7D4C0A692C3ED21E0050CB10 /* InAppPurchaseManager.entitlements */, + 7D4C0A6A2C3ED21E0050CB10 /* Preview Content */, + ); + path = InAppPurchaseManager; + sourceTree = ""; + }; + 7D4C0A6A2C3ED21E0050CB10 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 7D4C0A722C3ED2B70050CB10 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7D4C0A5F2C3ED21B0050CB10 /* InAppPurchaseManager */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7D4C0A6F2C3ED21E0050CB10 /* Build configuration list for PBXNativeTarget "InAppPurchaseManager" */; + buildPhases = ( + 7D4C0A5C2C3ED21B0050CB10 /* Sources */, + 7D4C0A5D2C3ED21B0050CB10 /* Frameworks */, + 7D4C0A5E2C3ED21B0050CB10 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = InAppPurchaseManager; + productName = InAppPurchaseManager; + productReference = 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7D4C0A582C3ED21B0050CB10 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 7D4C0A5F2C3ED21B0050CB10 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = 7D4C0A5B2C3ED21B0050CB10 /* Build configuration list for PBXProject "InAppPurchaseManager" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7D4C0A572C3ED21B0050CB10; + productRefGroup = 7D4C0A612C3ED21B0050CB10 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7D4C0A5F2C3ED21B0050CB10 /* InAppPurchaseManager */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7D4C0A5E2C3ED21B0050CB10 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D4C0A6C2C3ED21E0050CB10 /* Preview Assets.xcassets in Resources */, + 7D4C0A682C3ED21E0050CB10 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7D4C0A5C2C3ED21B0050CB10 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D4C0A762C3ED3570050CB10 /* store.swift in Sources */, + 7D4C0A792C3EDEB70050CB10 /* SubscriptionView.swift in Sources */, + 7D4C0A662C3ED21B0050CB10 /* ContentView.swift in Sources */, + 7D4C0A642C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7D4C0A6D2C3ED21E0050CB10 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7D4C0A6E2C3ED21E0050CB10 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 7D4C0A702C3ED21E0050CB10 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = InAppPurchaseManager/InAppPurchaseManager.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"InAppPurchaseManager/Preview Content\""; + DEVELOPMENT_TEAM = GR99S2ZJZQ; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.wangchujiang.InAppPurchaseManager; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7D4C0A712C3ED21E0050CB10 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = InAppPurchaseManager/InAppPurchaseManager.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"InAppPurchaseManager/Preview Content\""; + DEVELOPMENT_TEAM = GR99S2ZJZQ; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.wangchujiang.InAppPurchaseManager; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7D4C0A5B2C3ED21B0050CB10 /* Build configuration list for PBXProject "InAppPurchaseManager" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D4C0A6D2C3ED21E0050CB10 /* Debug */, + 7D4C0A6E2C3ED21E0050CB10 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7D4C0A6F2C3ED21E0050CB10 /* Build configuration list for PBXNativeTarget "InAppPurchaseManager" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D4C0A702C3ED21E0050CB10 /* Debug */, + 7D4C0A712C3ED21E0050CB10 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7D4C0A582C3ED21B0050CB10 /* Project object */; +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcuserdata/wangchujiang.xcuserdatad/UserInterfaceState.xcuserstate b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcuserdata/wangchujiang.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..6f96661 Binary files /dev/null and b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcuserdata/wangchujiang.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcshareddata/xcschemes/InAppPurchaseManager.xcscheme b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcshareddata/xcschemes/InAppPurchaseManager.xcscheme new file mode 100644 index 0000000..48e6b3a --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcshareddata/xcschemes/InAppPurchaseManager.xcscheme @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcuserdata/wangchujiang.xcuserdatad/xcschemes/xcschememanagement.plist b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcuserdata/wangchujiang.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..758ee82 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcuserdata/wangchujiang.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + InAppPurchaseManager.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + 7D4C0A5F2C3ED21B0050CB10 + + primary + + + + + diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..532cd72 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,63 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/Contents.json b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/ContentView.swift b/examples/InAppPurchaseManager/InAppPurchaseManager/ContentView.swift new file mode 100644 index 0000000..c109c8e --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/ContentView.swift @@ -0,0 +1,28 @@ +// +// ContentView.swift +// InAppPurchaseManager +// +// Created by 王楚江 on 2024/7/10. +// + +import SwiftUI + +struct ContentView: View { + @EnvironmentObject private var entitlement: EntitlementManager + @State var showingSubscriptionView = false + var body: some View { + VStack { + if entitlement.hasPro == false { + Button("Subscription Purchase") { + showingSubscriptionView.toggle() + } + } else { + Text("You have subscribed to purchase") + } + } + .padding() + .sheet(isPresented: $showingSubscriptionView) { + SubscriptionView() + } + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManager.entitlements b/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManager.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManager.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManagerApp.swift b/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManagerApp.swift new file mode 100644 index 0000000..24b1901 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManagerApp.swift @@ -0,0 +1,27 @@ +// +// InAppPurchaseManagerApp.swift +// InAppPurchaseManager +// +// Created by 王楚江 on 2024/7/10. +// + +import SwiftUI + +@main +struct InAppPurchaseManagerApp: App { + @StateObject private var entitlementManager: EntitlementManager + @StateObject private var subscriptionsManager: SubscriptionsManager + init() { + let entitlement = EntitlementManager() + let subscriptions = SubscriptionsManager(entitlementManager: entitlement) + self._entitlementManager = StateObject(wrappedValue: entitlement) + self._subscriptionsManager = StateObject(wrappedValue: subscriptions) + } + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(entitlementManager) + .environmentObject(subscriptionsManager) + } + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/Preview Content/Preview Assets.xcassets/Contents.json b/examples/InAppPurchaseManager/InAppPurchaseManager/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/SubscriptionView.swift b/examples/InAppPurchaseManager/InAppPurchaseManager/SubscriptionView.swift new file mode 100644 index 0000000..e218865 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/SubscriptionView.swift @@ -0,0 +1,61 @@ +// +// SubscriptionView.swift +// InAppPurchaseManager +// +// Created by 王楚江 on 2024/7/10. +// + +import SwiftUI +import StoreKit + +struct SubscriptionView: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject private var subscriptions: SubscriptionsManager + @State private var message: String = "" + let privacyPolicy = URL(string: "https://wangchujiang.com/copybook-generator/privacy-policy.html")! + let termsOfService = URL(string: "https://wangchujiang.com/copybook-generator/terms-of-service.html")! + var body: some View { + if !subscriptions.products.isEmpty { + VStack { + SubscriptionStoreView(productIDs: subscriptions.productIDs) + .storeButton(.visible, for: .policies) + .subscriptionStorePolicyDestination(url: privacyPolicy, for: .privacyPolicy) + .subscriptionStorePolicyDestination(url: termsOfService, for: .termsOfService) + .onInAppPurchaseCompletion(perform: { product, result in + if case .success(.success(let transaction)) = result { + print("Purchased successfully: \(transaction.signedDate)") + await subscriptions.updatePurchasedProducts() + dismiss() + } else { + print("Something else happened") + } + }) + Button(action: { + Task { + await subscriptions.restorePurchases() + dismiss() + } + }, label: { + Text("Restore Subscription") + }) + #if os(macOS) + .buttonStyle(.link) + #endif + .offset(y: -22) + } + .background(.background) + .frame(minWidth: 320, minHeight: 580) + .frame(maxWidth: 450) + } else { + VStack { + if message.isEmpty { + ProgressView().progressViewStyle(.circular).scaleEffect(1).ignoresSafeArea(.all) + } else { + Text(message).foregroundStyle(.red) + } + } + .padding(.horizontal) + .frame(minWidth: 230, minHeight: 120) + } + } +} diff --git a/examples/InAppPurchaseManager/InAppPurchaseManager/store.swift b/examples/InAppPurchaseManager/InAppPurchaseManager/store.swift new file mode 100644 index 0000000..7b18531 --- /dev/null +++ b/examples/InAppPurchaseManager/InAppPurchaseManager/store.swift @@ -0,0 +1,159 @@ +// +// store.swift +// InAppPurchaseManager +// +// Created by 王楚江 on 2024/7/10. +// + +import SwiftUI +import StoreKit + +/// 管理用户的权限状态 +class EntitlementManager: ObservableObject { + /// UserDefaults 实例,用于存储权限状态 + static let userDefaults = UserDefaults(suiteName: "com.wangchujiang.InAppPurchaseManager.vip")! + /// 使用 @AppStorage 将 hasPro 属性保存到 UserDefaults 中 + @AppStorage("hasPro", store: userDefaults) var hasPro: Bool = false +} + +/// 管理订阅产品和购买记录 +@MainActor class SubscriptionsManager: NSObject, ObservableObject { + /// 订阅产品的标识符数组 + let productIDs: [String] = [ + "com.wangchujiang.InAppPurchaseManager.monthly", + "com.wangchujiang.InAppPurchaseManager.yearly", + "com.wangchujiang.InAppPurchaseManager.lifetime" + ] + /// 记录已购买的产品标识符集合 + var purchasedProductIDs: Set = [] + /// 发布订阅产品信息 + @Published var products: [Product] = [] + /// 授权管理器 + private var entitlementManager: EntitlementManager? = nil + /// 更新任务 + private var updates: Task? = nil + /// 初始化方法,接收 EntitlementManager 实例作为参数 + init(entitlementManager: EntitlementManager) { + self.entitlementManager = entitlementManager + super.init() + // 监听交易更新 + self.updates = observeTransactionUpdates() + Task { + await self.loadProducts(action: { err, success in + + }) + } + // 添加自身作为 SKPaymentTransactionObserver + SKPaymentQueue.default().add(self) + } + // 析构方法,取消更新任务 + deinit { + updates?.cancel() + } + // MARK: - 观察交易更新 + /// 异步观察交易更新 + func observeTransactionUpdates() -> Task { + Task(priority: .background) { [unowned self] in + for await _ in Transaction.updates { + await self.updatePurchasedProducts() + } + } + } +} + +// MARK: - StoreKit2 API 扩展 +extension SubscriptionsManager { + // MARK: - 加载产品列表 + /// 异步加载产品列表 + func loadProducts(action: ((_ err: String?, _ success: Bool) -> Void)?) async { + do { + // 使用 Product.products(for:) 加载产品信息,并按价格排序 + self.products = try await Product.products(for: productIDs).sorted(by: { $0.price > $1.price }) + print("self.product: \(self.products)") + action?(nil, true) + } catch { + let errorString = String(localized: "Failed to get the product! Please check the network! \n\(error.localizedDescription)") + action?(errorString, false) + } + } + // MARK: - 购买产品 + /// 异步购买产品 + func buyProduct(_ product: Product) async { + do { + // 使用 product.purchase() 进行产品购买 + let result = try await product.purchase() + switch result { + case let .success(.verified(transaction)): + print("Successful purhcase 成功购买") + // 完成交易并更新已购买产品 + await transaction.finish() + await self.updatePurchasedProducts() + case let .success(.unverified(_, error)): + // Successful purchase but transaction/receipt can't be verified + // Could be a jailbroken phone + // 购买成功,但交易/收据无法验证 + // 可能是越狱手机 + print("Unverified purchase. Might be jailbroken. Error: \(error)") + break + case .pending: + // Transaction waiting on SCA (Strong Customer Authentication) or approval from Ask to Buy + // 等待 SCA(Strong Customer Authentication)或“要求购买”批准的交易 + break + case .userCancelled: + print("User cancelled!") + break + @unknown default: + print("Failed to purchase the product!") + break + } + } catch { + print("Failed to purchase the product!") + } + } + // MARK: - 更新已购买的产品 + /// 异步更新已购买的产品 + func updatePurchasedProducts() async { + /// 一系列最新交易,使用户有权进行应用内购买和订阅。 + for await result in Transaction.currentEntitlements { + guard case .verified(let transaction) = result else { + continue + } + if transaction.revocationDate == nil { + // 如果交易未被撤销,则将产品标识符添加到已购买集合中 + if !self.purchasedProductIDs.contains(transaction.productID) { + self.purchasedProductIDs.insert(transaction.productID) + } + } else { + // 如果交易被撤销,则从已购买集合中移除产品标识符 + self.purchasedProductIDs.remove(transaction.productID) + } + } + // 更新 EntitlementManager 的 hasPro 属性 + self.entitlementManager?.hasPro = !self.purchasedProductIDs.isEmpty + } + // MARK: - 恢复购买 + /// 异步恢复购买 + func restorePurchases() async { + do { + // 同步应用内购买信息 + try await AppStore.sync() + // 更新已购买的产品 + await updatePurchasedProducts() + } catch { + print(error) + } + } +} + +// MARK: - SKPaymentTransactionObserver 实现 +extension SubscriptionsManager: SKPaymentTransactionObserver { + // 支付队列更新交易 + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + print("Subscriptions Payment Queue! updated!") + } + // 应用内购买准备添加到支付队列 + func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { + print("Subscriptions Payment Queue! Should Add Store Payment!") + return true + } +} diff --git a/examples/InAppPurchaseManager/Product.storekit b/examples/InAppPurchaseManager/Product.storekit new file mode 100644 index 0000000..ff4515c --- /dev/null +++ b/examples/InAppPurchaseManager/Product.storekit @@ -0,0 +1,166 @@ +{ + "identifier" : "8F58A673", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_failTransactionsEnabled" : false, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "2F793903", + "localizations" : [ + + ], + "name" : "VIP Pro Example", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "0.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "FF45B2EF", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "All Access Monthly", + "displayName" : "Pro Monthly", + "locale" : "en_US" + }, + { + "description" : "每月全部访问", + "displayName" : "专业月会员", + "locale" : "zh_CN" + } + ], + "productID" : "com.wangchujiang.InAppPurchaseManager.monthly", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Pro Monthly", + "subscriptionGroupID" : "2F793903", + "type" : "RecurringSubscription" + }, + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "12.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "908E4B0F", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "All Access Annually", + "displayName" : "Pro Yearly", + "locale" : "en_US" + }, + { + "description" : "每年全部访问", + "displayName" : "专业年会员", + "locale" : "zh_CN" + } + ], + "productID" : "com.wangchujiang.InAppPurchaseManager.yearly", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Pro Yearly", + "subscriptionGroupID" : "2F793903", + "type" : "RecurringSubscription" + }, + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "66.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "26539A2F", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "All Access Lifetime", + "displayName" : "Pro Lifetime", + "locale" : "en_US" + }, + { + "description" : "全部终生访问", + "displayName" : "专业终生版", + "locale" : "zh_CN" + } + ], + "productID" : "com.wangchujiang.InAppPurchaseManager.lifetime", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Pro Lifetime", + "subscriptionGroupID" : "2F793903", + "type" : "RecurringSubscription" + } + ] + } + ], + "version" : { + "major" : 3, + "minor" : 0 + } +}