@@ -0,0 +1,376 @@
//
// I n A p p R e c e i p t + O b j c . s w i f t
// T P I n A p p R e c e i p t
//
// C r e a t e d b y P a v e l T i k h o n e n k o o n 2 4 . 0 5 . 2 0 2 1 .
// C o p y r i g h t © 2 0 2 0 - 2 0 2 1 P a v e l T i k h o n e n k o . A l l r i g h t s r e s e r v e d .
//
import Foundation
import TPInAppReceipt
// MARK: - I n A p p R e c e i p t
@objc ( InAppReceipt ) public class InAppReceipt_Objc : NSObject
{
private var wrappedReceipt : InAppReceipt
// / C r e a t e s a n d r e t u r n s t h e ' I n A p p R e c e i p t ' i n s t a n c e f r o m d a t a o b j e c t
// /
// / - R e t u r n s : ' I n A p p R e c e i p t ' i n s t a n c e
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f ` I n A p p R e c e i p t ` c a n n o t b e c r e a t e d .
@objc public class func receipt ( from data : Data ) throws -> InAppReceipt_Objc
{
return try InAppReceipt_Objc ( receiptData : data )
}
// / C r e a t e s a n d r e t u r n s t h e ' I n A p p R e c e i p t ' i n s t a n c e f r o m d a t a o b j e c t
// /
// / - R e t u r n s : ' I n A p p R e c e i p t ' i n s t a n c e
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f ` I n A p p R e c e i p t ` c a n n o t b e c r e a t e d .
@objc public class func receipt ( from data : Data ) -> InAppReceipt_Objc ?
{
return try ? InAppReceipt_Objc ( receiptData : data )
}
// / C r e a t e s a n d r e t u r n s t h e ' I n A p p R e c e i p t ' i n s t a n c e u s i n g l o c a l r e c e i p t
// /
// / - R e t u r n s : ' I n A p p R e c e i p t ' i n s t a n c e
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f ` I n A p p R e c e i p t ` c a n n o t b e c r e a t e d .
@objc public class func local ( ) throws -> InAppReceipt_Objc
{
let data = try Bundle . main . appStoreReceiptData ( )
return try InAppReceipt_Objc . receipt ( from : data )
}
// / C r e a t e s a n d r e t u r n s t h e ' I n A p p R e c e i p t ' i n s t a n c e u s i n g l o c a l r e c e i p t
// /
// / - R e t u r n s : ' I n A p p R e c e i p t ' i n s t a n c e
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f ` I n A p p R e c e i p t ` c a n n o t b e c r e a t e d .
@objc public class func local ( ) -> InAppReceipt_Objc ?
{
guard let data = try ? Bundle . main . appStoreReceiptData ( ) else { return nil }
return InAppReceipt_Objc . receipt ( from : data )
}
// /
// /
// / I n i t i a l i z e a ` I n A p p R e c e i p t ` w i t h a s n 1 p a y l o a d
// /
// / - p a r a m e t e r r e c e i p t D a t a : ` D a t a ` o b j e c t t h a t r e p r e s e n t s r e c e i p t
@objc public init ( receiptData : Data , rootCertPath : String ? = nil ) throws
{
self . wrappedReceipt = try InAppReceipt . init ( receiptData : receiptData , rootCertPath : rootCertPath )
}
}
@objc public extension InAppReceipt_Objc
{
// / T h e a p p ’ s b u n d l e i d e n t i f i e r
var bundleIdentifier : String
{
return wrappedReceipt . bundleIdentifier
}
// / T h e a p p ’ s v e r s i o n n u m b e r
var appVersion : String
{
return wrappedReceipt . appVersion
}
// / T h e v e r s i o n o f t h e a p p t h a t w a s o r i g i n a l l y p u r c h a s e d .
var originalAppVersion : String
{
return wrappedReceipt . originalAppVersion
}
// / I n - a p p p u r c h a s e ' s r e c e i p t s
var purchases : [ InAppPurchase_Objc ]
{
return wrappedReceipt . purchases . map { . init ( purchase : $0 ) }
}
// / R e t u r n s a l l a u t o r e n e w a b l e ` I n A p p P u r c h a s e ` s ,
var autoRenewablePurchases : [ InAppPurchase_Objc ]
{
return wrappedReceipt . purchases . filter ( { $0 . isRenewableSubscription } ) . map { . init ( purchase : $0 ) }
}
// / R e t u r n s a l l A C T I V E a u t o r e n e w a b l e ` I n A p p P u r c h a s e ` s ,
// /
var activeAutoRenewableSubscriptionPurchases : [ InAppPurchase_Objc ]
{
return wrappedReceipt . purchases . filter ( { $0 . isRenewableSubscription && $0 . isActiveAutoRenewableSubscription ( forDate : Date ( ) ) } ) . map { . init ( purchase : $0 ) }
}
// / T h e d a t e t h a t t h e a p p r e c e i p t e x p i r e s
var expirationDate : Date ?
{
return wrappedReceipt . expirationDate
}
// / R e t u r n s ` t r u e ` i f a n y p u r c h a s e s e x i s t , ` f a l s e ` o t h e r w i s e
var hasPurchases : Bool
{
return purchases . count > 0
}
// / R e t u r n s ` t r u e ` i f a n y A c t i v e A u t o R e n e w a b l e p u r c h a s e s e x i s t , ` f a l s e ` o t h e r w i s e
var hasActiveAutoRenewablePurchases : Bool
{
return activeAutoRenewableSubscriptionPurchases . count > 0
}
var creationDate : Date
{
return wrappedReceipt . creationDate
}
var ageRating : String
{
return wrappedReceipt . ageRating
}
// / I n A p p R e c e i p t i n b a s e 6 4
var base64 : String
{
return wrappedReceipt . base64
}
// / R e t u r n o r i g i n a l t r a n s a c t i o n i d e n t i f i e r i f t h e r e i s a p u r c h a s e f o r a s p e c i f i c p r o d u c t i d e n t i f i e r
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
func originalTransactionIdentifier ( ofProductIdentifier productIdentifier : String ) -> String ?
{
return purchases ( ofProductIdentifier : productIdentifier ) . first ? . originalTransactionIdentifier
}
// / R e t u r n s ` t r u e ` i f t h e r e i s a p u r c h a s e f o r a s p e c i f i c p r o d u c t i d e n t i f i e r , ` f a l s e ` o t h e r w i s e
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
func containsPurchase ( ofProductIdentifier productIdentifier : String ) -> Bool
{
for item in purchases
{
if item . productIdentifier = = productIdentifier
{
return true
}
}
return false
}
// / R e t u r n s ` [ I n A p p P u r c h a s e ] ` i f t h e r e a r e p u r c h a s e s f o r a s p e c i f i c p r o d u c t i d e n t i f i e r ,
// / e m p t y a r r a y o t h e r w i s e
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
// / - p a r a m e t e r s o r t : S o r t i n g b l o c k
func purchases ( ofProductIdentifier productIdentifier : String ,
sortedBy sort : ( ( InAppPurchase_Objc , InAppPurchase_Objc ) -> Bool ) ? = nil ) -> [ InAppPurchase_Objc ]
{
let filtered : [ InAppPurchase_Objc ] = purchases . filter ( {
return $0 . productIdentifier = = productIdentifier
} )
if let sort = sort
{
return filtered . sorted ( by : {
return sort ( $0 , $1 )
} )
} else {
return filtered . sorted ( by : {
return $0 . purchaseDate > $1 . purchaseDate
} )
}
}
// / R e t u r n s ` I n A p p P u r c h a s e ` i f t h e r e i s a p u r c h a s e f o r a s p e c i f i c p r o d u c t i d e n t i f i e r ,
// / ` n i l ` o t h e r w i s e
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
func activeAutoRenewableSubscriptionPurchases ( ofProductIdentifier productIdentifier : String , forDate date : Date ) -> InAppPurchase_Objc ?
{
let filtered = purchases ( ofProductIdentifier : productIdentifier )
for purchase in filtered
{
if purchase . isActiveAutoRenewableSubscription ( forDate : date )
{
return purchase
}
}
return nil
}
// / R e t u r n s t h e l a s t ` I n A p p P u r c h a s e ` i f t h e r e i s o n e f o r a s p e c i f i c p r o d u c t i d e n t i f i e r ,
// / ` n i l ` o t h e r w i s e
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
func lastAutoRenewableSubscriptionPurchase ( ofProductIdentifier productIdentifier : String ) -> InAppPurchase_Objc ?
{
var purchase : InAppPurchase_Objc ? = nil
let filtered = purchases ( ofProductIdentifier : productIdentifier )
var lastInterval : TimeInterval = 0
for iap in filtered
{
if ! ( iap . productIdentifier = = productIdentifier ) {
continue
}
if let thisInterval = iap . subscriptionExpirationDate ? . timeIntervalSince1970 {
if purchase = = nil || thisInterval > lastInterval {
purchase = iap
lastInterval = thisInterval
}
}
}
return purchase
}
// / R e t u r n s t r u e i f t h e r e i s a n a c t i v e s u b s c r i p t i o n f o r a s p e c i f i c p r o d u c t i d e n t i f i e r o n t h e d a t e s p e c i f i e d ,
// / f a l s e o t h e r w i s e
// /
// / - p a r a m e t e r p r o d u c t I d e n t i f i e r : P r o d u c t n a m e
// / - p a r a m e t e r d a t e : D a t e t o c h e c k s u b s c r i p t i o n a g a i n s t
func hasActiveAutoRenewableSubscription ( ofProductIdentifier productIdentifier : String , forDate date : Date ) -> Bool
{
return activeAutoRenewableSubscriptionPurchases ( ofProductIdentifier : productIdentifier , forDate : date ) != nil
}
}
// MARK: - I n A p p P u r c h a s e
@objc ( InAppPurchase ) public class InAppPurchase_Objc : NSObject
{
@objc public enum ` Type ` : Int32
{
// / T y p e t h a t w e c a n ' t r e c o g n i z e f o r s o m e r e a s o n
case unknown = - 1
// / T y p e t h a t c u s t o m e r s p u r c h a s e o n c e . T h e y d o n ' t e x p i r e .
case nonConsumable
// / T y p e t h a t a r e d e p l e t e d a f t e r o n e u s e . C u s t o m e r s c a n p u r c h a s e t h e m m u l t i p l e t i m e s .
case consumable
// / T y p e t h a t c u s t o m e r s p u r c h a s e o n c e a n d t h a t r e n e w a u t o m a t i c a l l y o n a r e c u r r i n g b a s i s u n t i l c u s t o m e r s d e c i d e t o c a n c e l .
case nonRenewingSubscription
// / T y p e t h a t c u s t o m e r s p u r c h a s e a n d i t p r o v i d e s a c c e s s o v e r a l i m i t e d d u r a t i o n a n d d o n ' t r e n e w a u t o m a t i c a l l y . C u s t o m e r s c a n p u r c h a s e t h e m a g a i n .
case autoRenewableSubscription
}
private let purchase : InAppPurchase
// / T h e p r o d u c t i d e n t i f i e r w h i c h p u r c h a s e r e l a t e d t o
@objc public var productIdentifier : String { purchase . productIdentifier }
// / P r o d u c t t y p e
@objc public var productType : Type { Type ( rawValue : purchase . productType . rawValue ) ? ? . unknown }
// / T r a n s a c t i o n i d e n t i f i e r
@objc public var transactionIdentifier : String { purchase . transactionIdentifier }
// / O r i g i n a l T r a n s a c t i o n i d e n t i f i e r
@objc public var originalTransactionIdentifier : String { purchase . originalTransactionIdentifier }
// / P u r c h a s e D a t e
@objc public var purchaseDate : Date { purchase . purchaseDate }
// / O r i g i n a l P u r c h a s e D a t e
@objc public var originalPurchaseDate : Date { purchase . originalPurchaseDate }
// / S u b s c r i p t i o n E x p i r a t i o n D a t e . R e t u r n s ` n i l ` i f t h e p u r c h a s e h a s b e e n e x p i r e d ( i n s o m e c a s e s )
@objc public var subscriptionExpirationDate : Date ? { purchase . subscriptionExpirationDate }
// / C a n c e l l a t i o n D a t e . R e t u r n s ` n i l ` i f t h e p u r c h a s e i s n o t a r e n e w a b l e s u b s c r i p t i o n
@objc public var cancellationDate : Date ? { purchase . cancellationDate }
// / T h i s v a l u e i s ` t r u e ` i f t h e c u s t o m e r ’ s s u b s c r i p t i o n i s c u r r e n t l y i n t h e f r e e t r i a l p e r i o d , o r ` f a l s e ` i f n o t .
@objc public var subscriptionTrialPeriod : Bool { purchase . subscriptionTrialPeriod }
// / T h i s v a l u e i s ` t r u e ` i f t h e c u s t o m e r ’ s s u b s c r i p t i o n i s c u r r e n t l y i n a n i n t r o d u c t o r y p r i c e p e r i o d , o r ` f a l s e ` i f n o t .
@objc public var subscriptionIntroductoryPricePeriod : Bool { purchase . subscriptionIntroductoryPricePeriod }
// / A u n i q u e i d e n t i f i e r f o r p u r c h a s e e v e n t s a c r o s s d e v i c e s , i n c l u d i n g s u b s c r i p t i o n - r e n e w a l e v e n t s . T h i s v a l u e i s t h e p r i m a r y k e y f o r i d e n t i f y i n g s u b s c r i p t i o n p u r c h a s e s .
@objc public var webOrderLineItemID : Int { purchase . webOrderLineItemID ? ? NSNotFound }
// / T h e v a l u e i s a n i d e n t i f i e r o f t h e s u b s c r i p t i o n o f f e r t h a t t h e u s e r r e d e e m e d .
// / R e t u r n s ` n i l ` i f t h e u s e r d i d n ' t u s e a n y s u b s c r i p t i o n o f f e r s .
@objc public var promotionalOfferIdentifier : String ? { purchase . promotionalOfferIdentifier }
// / T h e n u m b e r o f c o n s u m a b l e p r o d u c t s p u r c h a s e d
// / T h e d e f a u l t v a l u e i s ` 1 ` u n l e s s m o d i f i e d w i t h a m u t a b l e p a y m e n t . T h e m a x i m u m v a l u e i s 1 0 .
@objc public var quantity : Int { purchase . quantity }
init ( purchase : InAppPurchase )
{
self . purchase = purchase
}
}
public extension InAppPurchase_Objc
{
// / A B o o l e a n v a l u e i n d i c a t i n g w h e t h e r t h e p u r c h a s e i s r e n e w a b l e s u b s c r i p t i o n .
var isRenewableSubscription : Bool
{
return purchase . isRenewableSubscription
}
// / C h e c k w h e t h e r t h e s u b s c r i p t i o n i s a c t i v e f o r a s p e c i f i c d a t e
// /
// / - P a r a m e t e r d a t e : T h e d a t e i n w h i c h t h e a u t o - r e n e w a b l e s u b s c r i p t i o n s h o u l d b e a c t i v e .
// / - R e t u r n s : t r u e i f t h e l a t e s t a u t o - r e n e w a b l e s u b s c r i p t i o n i s a c t i v e f o r t h e g i v e n d a t e , f a l s e o t h e r w i s e .
func isActiveAutoRenewableSubscription ( forDate date : Date ) -> Bool
{
return purchase . isActiveAutoRenewableSubscription ( forDate : date )
}
}
// MARK: - V a l i d a t i o n
// / A I n A p p R e c e i p t e x t e n s i o n h e l p s t o v a l i d a t e t h e r e c e i p t
@objc public extension InAppReceipt_Objc
{
// / V e r i f y I n A p p R e c e i p t
// /
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f v e r i f i c a t i o n f a i l s
func verify ( ) throws
{
try wrappedReceipt . verifyHash ( )
try wrappedReceipt . verifyBundleIdentifierAndVersion ( )
try wrappedReceipt . verifySignature ( )
}
// / V e r i f y o n l y h a s h
// / S h o u l d b e e q u a l t o ` r e c e i p t H a s h ` v a l u e
// /
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f v e r i f i c a t i o n f a i l s
func verifyHash ( ) throws
{
try wrappedReceipt . verifyHash ( )
}
// / V e r i f y t h a t t h e b u n d l e i d e n t i f i e r i n t h e r e c e i p t m a t c h e s a h a r d - c o d e d c o n s t a n t c o n t a i n i n g t h e C F B u n d l e I d e n t i f i e r v a l u e y o u e x p e c t i n t h e I n f o . p l i s t f i l e . I f t h e y d o n o t m a t c h , v a l i d a t i o n f a i l s .
// / V e r i f y t h a t t h e v e r s i o n i d e n t i f i e r s t r i n g i n t h e r e c e i p t m a t c h e s a h a r d - c o d e d c o n s t a n t c o n t a i n i n g t h e C F B u n d l e S h o r t V e r s i o n S t r i n g v a l u e ( f o r m a c O S ) o r t h e C F B u n d l e V e r s i o n v a l u e ( f o r i O S ) t h a t y o u e x p e c t i n t h e I n f o . p l i s t f i l e .
// /
// /
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f v e r i f i c a t i o n f a i l s
func verifyBundleIdentifierAndVersion ( ) throws
{
try wrappedReceipt . verifyBundleIdentifierAndVersion ( )
}
// / V e r i f y s i g n a t u r e i n s i d e p k c s 7 c o n t a i n e r
// /
// / - t h r o w s : A n e r r o r i n t h e I n A p p R e c e i p t d o m a i n , i f v e r i f i c a t i o n c a n ' t b e c o m p l e t e d
func verifySignature ( ) throws
{
try wrappedReceipt . verifySignature ( )
}
}