{"id":21373581,"url":"https://github.com/itenfay/dyfstorekit","last_synced_at":"2025-04-14T14:33:35.498Z","repository":{"id":56908108,"uuid":"186654936","full_name":"itenfay/DYFStoreKit","owner":"itenfay","description":"iOS内购(This is used for in app purchases on iOS) [Swift Version]：https://github.com/itenfay/DYFStore","archived":false,"fork":false,"pushed_at":"2024-06-23T11:00:27.000Z","size":544,"stargazers_count":97,"open_issues_count":3,"forks_count":11,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T03:32:33.470Z","etag":null,"topics":["iap","in-app-purchase","in-app-purchases","ios","objective-c","oc","pay","payment","purchase","receipt","receipt-data","receipt-url","receipt-verification","receipt-verifier","skpayment","skpaymentqueue","skpaymenttransaction","skproduct","storekit","transaction"],"latest_commit_sha":null,"homepage":"https://www.jianshu.com/p/de030cd6e4a3","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/itenfay.png","metadata":{"files":{"readme":"README-en.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-14T15:55:25.000Z","updated_at":"2025-03-27T02:37:10.000Z","dependencies_parsed_at":"2024-06-02T10:13:57.482Z","dependency_job_id":"db621531-6efb-4446-9cd4-f81fc0e92b44","html_url":"https://github.com/itenfay/DYFStoreKit","commit_stats":{"total_commits":49,"total_committers":3,"mean_commits":"16.333333333333332","dds":"0.22448979591836737","last_synced_commit":"6427182aa39c8f9bd1957cfd9e4641e0b1b263ee"},"previous_names":["itenfay/dyfstorekit","chenxing640/dyfstorekit"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itenfay%2FDYFStoreKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itenfay%2FDYFStoreKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itenfay%2FDYFStoreKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itenfay%2FDYFStoreKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/itenfay","download_url":"https://codeload.github.com/itenfay/DYFStoreKit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248897595,"owners_count":21179615,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["iap","in-app-purchase","in-app-purchases","ios","objective-c","oc","pay","payment","purchase","receipt","receipt-data","receipt-url","receipt-verification","receipt-verifier","skpayment","skpaymentqueue","skpaymenttransaction","skproduct","storekit","transaction"],"created_at":"2024-11-22T08:29:21.996Z","updated_at":"2025-04-14T14:33:35.460Z","avatar_url":"https://github.com/itenfay.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"[中文版](README.md) | **English Version**\n\n\n## DYFStoreKit\n\nA lightweight and easy-to-use iOS library for In-App Purchases([Swift Version](https://github.com/itenfay/DYFStore)).\n\n`DYFStoreKit` uses blocks and [notifications](#Notifications) to wrap `StoreKit`, provides [receipt verification](#Receipt-verification) and [transaction persistence](#Transaction-persistence).\n\n[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](LICENSE)\u0026nbsp;\n[![CocoaPods](http://img.shields.io/cocoapods/v/DYFStoreKit.svg?style=flat)](http://cocoapods.org/pods/DYFStoreKit)\u0026nbsp;\n![CocoaPods](http://img.shields.io/cocoapods/p/DYFStoreKit.svg?style=flat)\u0026nbsp;\n\n\n## Related Links\n\n- [DYFRuntimeProvider](https://github.com/itenfay/DYFRuntimeProvider/)\n- [DYFKeychain](https://github.com/itenfay/DYFKeychain/)\n- [DYFStoreReceiptVerifier](https://github.com/itenfay/DYFStoreReceiptVerifier/)\n- [Unity-iOS-InAppPurchase](https://github.com/itenfay/Unity-iOS-InAppPurchase/)\n- [in-app-purchase-complete-programming-guide-for-iOS](https://itenfay.github.io/2016/10/16/in-app-purchase-complete-programming-guide-for-iOS/)\n- [how-to-easily-complete-in-app-purchase-configuration-for-iOS](https://itenfay.github.io/2016/10/12/how-to-easily-complete-in-app-purchase-configuration-for-iOS/)\n\n\n## Features\n\n- Super simple in-app purchases.\n- Built-in support for remembering your purchases.\n- Built-in receipt validation (remote).\n- Built-in hosted content downloads and notifications.\n\n\n## Group (ID:614799921)\n\n\u003cdiv align=left\u003e\n\u0026emsp; \u003cimg src=\"https://github.com/itenfay/DYFStoreKit/raw/master/images/g614799921.jpg\" width=\"30%\" /\u003e\n\u003c/div\u003e\n\n\n## Installation\n\nUsing [CocoaPods](https://cocoapods.org):\n\n``` \npod 'DYFStoreKit'\n```\n\nOr\n\n```\npod 'DYFStoreKit', '~\u003e 2.2.0'\n```\n\nCheck out the [wiki](https://github.com/itenfay/DYFStoreKit/wiki/Installation) for more options.\n\n\n## Usage\n\nNext I'll show you how to use `DYFStoreKit`.\n\n### Initialization\n\nThe initialization is as follows.\n\n- Adds the observer of transactions and monitors the change of transactions.\n- Instantiates data persistent object and stores the related information of transactions.\n- Follows the agreement `DYFStoreAppStorePaymentDelegate` and processes payments for products purchased from the App Store.\n\n```\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions \n{\n    [self initIAPSDK];\n    return YES;\n}\n\n- (void)initIAPSDK\n{\n    [SKIAPManager.shared addStoreObserver];\n    \n    // Adds an observer that responds to updated transactions to the payment queue.\n    // If an application quits when transactions are still being processed, those transactions are not lost. The next time the application launches, the payment queue will resume processing the transactions. Your application should always expect to be notified of completed transactions.\n    // If more than one transaction observer is attached to the payment queue, no guarantees are made as to the order they will be called in. It is recommended that you use a single observer to process and finish the transaction.\n    [DYFStore.defaultStore addPaymentTransactionObserver];\n    \n    // Sets the delegate processes the purchase which was initiated by user from the App Store.\n    DYFStore.defaultStore.delegate = self;\n}\n```\n\nYou can process the purchase which was initiated by user from the App Store and provide your own implementation using the `DYFStoreAppStorePaymentDelegate` protocol:\n\n```\n// Processes the purchase which was initiated by user from the App Store.\n- (void)didReceiveAppStorePurchaseRequest:(SKPaymentQueue *)queue payment:(SKPayment *)payment forProduct:(SKProduct *)product\n{\n    if (![DYFStore canMakePayments]) {\n        [self showTipsMessage:@\"Your device is not able or allowed to make payments!\"];\n        return;\n    }\n    \n    // Get account name from your own user system.\n    NSString *accountName = @\"Handsome Jon\";\n    // This algorithm is negotiated with server developer.\n    NSString *userIdentifier = DYFCryptoSHA256(accountName);\n    DYFStoreLog(@\"userIdentifier: %@\", userIdentifier);\n    \n    [SKIAPManager.shared addPayment:product.productIdentifier userIdentifier:userIdentifier];\n}\n```\n\n### Request products\n\nYou need to check whether the device is not able or allowed to make payments before requesting products.\n\n```\nif (![DYFStore canMakePayments]) {\n    [self showTipsMessage:@\"Your device is not able or allowed to make payments!\"];\n    return;\n}\n```\n\nTo begin the purchase process, your app must know its product identifiers. There are two strategies for retrieving information about the products from the App Store.\n\n**Strategy 1:** Your app can uses a product identifier to fetch information about product available for sale in the App Store and to submit payment request directly.\n\n```\n- (IBAction)fetchesProductAndSubmitsPayment:(id)sender\n{\n    // You need to check whether the device is not able or allowed to make payments before requesting product.\n    if (![DYFStore canMakePayments]) {\n        [self showTipsMessage:@\"Your device is not able or allowed to make payments!\"];\n        return;\n    }\n    [self showLoading:@\"Loading...\"];\n    \n    NSArray *productIds = [self fetchProductIdentifiersFromServer];\n    NSUInteger index = arc4random_uniform((uint32_t)productIds.count);\n    NSString *productId = productIds[index];\n    [DYFStore.defaultStore requestProductWithIdentifier:productId success:^(NSArray *products, NSArray *invalidIdentifiers) {\n        [self hideLoading];\n        if (products.count == 1) {\n            NSString *productId = ((SKProduct *)products[0]).productIdentifier;\n            [self addPayment:productId];\n        } else {\n            [self showTipsMessage:@\"There is no this product for sale!\"];\n        }\n    } failure:^(NSError *error) {\n        [self hideLoading];\n        NSString *value = error.userInfo[NSLocalizedDescriptionKey];\n        NSString *msg = value ?: error.localizedDescription;\n        // This indicates that the product cannot be fetched, because an error was reported.\n        [self sendNotice:[NSString stringWithFormat:@\"An error occurs, %zi, %@\", error.code, msg]];\n    }];\n}\n\n- (void)addPayment:(NSString *)productId\n{\n    // Get account name from your own user system.\n    NSString *accountName = @\"Handsome Jon\";\n    // This algorithm is negotiated with server developer.\n    NSString *userIdentifier = DYFCryptoSHA256(accountName);\n    DYFStoreLog(@\"userIdentifier: %@\", userIdentifier);\n    [SKIAPManager.shared addPayment:productId userIdentifier:userIdentifier];\n}\n```\n\n**Strategy 2:** It can retrieve information about the products from the App Store and present its store UI to the user. Every product sold in your app has a unique product identifier. Your app uses these product identifiers to fetch information about products available for sale in the App Store, such as pricing, and to submit payment requests when users purchase those products.\n\n```\n- (NSArray *)fetchProductIdentifiersFromServer\n{\n    NSArray *productIds = @[@\"com.hncs.szj.coin42\",   // 42 gold coins for ￥6.\n                            @\"com.hncs.szj.coin210\",  // 210 gold coins for ￥30.\n                            @\"com.hncs.szj.coin686\",  // 686 gold coins for ￥98.\n                            @\"com.hncs.szj.coin1386\", // 1386 gold coins for ￥198.\n                            @\"com.hncs.szj.coin2086\", // 2086 gold coins for ￥298.\n                            @\"com.hncs.szj.coin4886\", // 4886 gold coins for ￥698.\n                            @\"com.hncs.szj.vip1\",     // non-renewable vip subscription for a month.\n                            @\"com.hncs.szj.vip2\"      // Auto-renewable vip subscription for three months.\n    ];\n    return productIds;\n}\n\n- (IBAction)fetchesProductsFromAppStore:(id)sender\n{\n    // You need to check whether the device is not able or allowed to make payments before requesting products.\n    if (![DYFStore canMakePayments]) {\n        [self showTipsMessage:@\"Your device is not able or allowed to make payments!\"];\n        return;\n    }\n    [self showLoading:@\"Loading...\"];\n    \n    NSArray *productIds = [self fetchProductIdentifiersFromServer];\n    [DYFStore.defaultStore requestProductWithIdentifiers:productIds success:^(NSArray *products, NSArray *invalidIdentifiers) {\n        [self hideLoading];\n        if (products.count \u003e 0) {\n            [self processData:products];\n        } else if (products.count == 0 \u0026\u0026 invalidIdentifiers.count \u003e 0) {\n            // Please check the product information you set up.\n            [self showTipsMessage:@\"There are no products for sale!\"];\n        }\n    } failure:^(NSError *error) {\n        [self hideLoading];\n        NSString *value = error.userInfo[NSLocalizedDescriptionKey];\n        NSString *msg = value ?: error.localizedDescription;\n        // This indicates that the products cannot be fetched, because an error was reported.\n        [self sendNotice:[NSString stringWithFormat:@\"An error occurs, %zi, %@\", error.code, msg]];\n    }];\n}\n\n- (void)processData:(NSArray *)products\n{\n    NSMutableArray *modelArray = [NSMutableArray arrayWithCapacity:0];\n    for (SKProduct *product in products) {\n        SKStoreProduct *p = [[SKStoreProduct alloc] init];\n        p.identifier = product.productIdentifier;\n        p.name = product.localizedTitle;\n        p.price = [product.price stringValue];\n        p.localePrice = [DYFStore.defaultStore localizedPriceOfProduct:product];\n        p.localizedDescription = product.localizedDescription;\n        [modelArray addObject:p];\n    }\n    [self displayStoreUI:modelArray];\n}\n\n- (void)displayStoreUI:(NSMutableArray *)dataArray\n{\n    SKStoreViewController *storeVC = [[SKStoreViewController alloc] init];\n    storeVC.dataArray = dataArray;\n    [self.navigationController pushViewController:storeVC animated:YES];\n}\n```\n\n### Add payment\n\nRequests payment of the product with the given product identifier.\n\n```\n[DYFStore.defaultStore purchaseProduct:@\"com.hncs.szj.coin210\"];\n```\n\nIf you need an opaque identifier for the user’s account on your system to add payment, you can use a one-way hash of the user’s account name to calculate the value for this property.\n\nCalculates the SHA256 hash function:\n\n```\nCG_INLINE NSString *DYFCryptoSHA256(NSString *string)\n{\n    const int digestLength = CC_SHA256_DIGEST_LENGTH; // 32\n    unsigned char md[digestLength];\n    const char *cStr = [string UTF8String];\n    size_t cStrLen = strlen(cStr);\n    \n    // Confirm that the length of C string is small enough\n    // to be recast when calling the hash function.\n    if (cStrLen \u003e UINT32_MAX) {\n        NSLog(@\"C string too long to hash: %@\", string);\n        return nil;\n    }\n    \n    CC_SHA256(cStr, (CC_LONG)cStrLen, md);\n    // Convert the array of bytes into a string showing its hex represention.\n    NSMutableString *hash = [NSMutableString string];\n    for (int i = 0; i \u003c digestLength; i++) {\n        // Add a dash every four bytes, for readability.\n        if (i != 0 \u0026\u0026 i%4 == 0) {\n            //[hash appendString:@\"-\"];\n        }\n        [hash appendFormat:@\"%02x\", md[i]];\n    }\n    \n    return hash;\n}\n```\n\nRequests payment of the product with the given product identifier, an opaque identifier for the user’s account on your system.\n\n```\n[DYFStore.defaultStore purchaseProduct:@\"com.hncs.szj.coin210\" userIdentifier:@\"A43512564ACBEF687924646CAFEFBDCAEDF4155125657\"];\n```\n\n### Restore transactions\n\n- Restores transactions without the user account identifier.\n\n```\n[DYFStore.defaultStore restoreTransactions];\n```\n\n- Restores transactions with the user account identifier.\n\n```\n[DYFStore.defaultStore restoreTransactions:@\"A43512564ACBEF687924646CAFEFBDCAEDF4155125657\"];\n```\n\n### Refresh receipt\n\nIf `Bundle.main.appStoreReceiptURL` is null, you need to create a refresh receipt request to obtain a receipt for a payment transaction.\n\n```\n[DYFStore.defaultStore refreshReceiptOnSuccess:^{\n    [self storeReceipt];\n} failure:^(NSError *error) {\n    [self failToRefreshReceipt];\n}];\n```\n\n### Notifications\n\n`DYFStoreKit` sends notifications of `StoreKit` related events and extends `NSNotification` to provide relevant information. To receive them, add the observer to a `DYFStoreKit` manager.\n\n#### Add the store observer\n\n```\n- (void)addStoreObserver \n{\n    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(processPurchaseNotification:) name:DYFStorePurchasedNotification object:nil];\n    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(processDownloadNotification:) name:DYFStoreDownloadedNotification object:nil];\n}\n```\n\n#### Remove the store observer\n\nWhen the application exits, you need to remove the store observer.\n\n```\n- (void)removeStoreObserver \n{\n    [NSNotificationCenter.defaultCenter removeObserver:self name:DYFStorePurchasedNotification object:nil];\n    [NSNotificationCenter.defaultCenter removeObserver:self name:DYFStoreDownloadedNotification object:nil];\n}\n```\n\n#### Payment transaction notifications\n\nPayment transaction notifications are sent after a payment has been requested or for each restored transaction.\n\n```\n- (void)processPurchaseNotification:(NSNotification *)notification\n{\n    [self hideLoading];\n    self.purchaseInfo = notification.object;\n    switch (self.purchaseInfo.state) {\n        case DYFStorePurchaseStatePurchasing:\n            [self showLoading:@\"Purchasing...\"];\n            break;\n        case DYFStorePurchaseStateCancelled:\n            [self sendNotice:@\"You cancel the purchase\"];\n            break;\n        case DYFStorePurchaseStateFailed:\n            [self sendNotice:[NSString stringWithFormat:@\"An error occurred, %zi\", self.purchaseInfo.error.code]];\n            break;\n        case DYFStorePurchaseStateSucceeded:\n        case DYFStorePurchaseStateRestored:\n            [self completePayment];\n            break;\n        case DYFStorePurchaseStateRestoreFailed:\n            [self sendNotice:[NSString stringWithFormat:@\"An error occurred, %zi\", self.purchaseInfo.error.code]];\n            break;\n        case DYFStorePurchaseStateDeferred:\n            DYFStoreLog(@\"Deferred\");\n            break;\n        default:\n            break;\n    }\n}\n```\n\n#### Download notifications\n\n```\n- (void)processDownloadNotification:(NSNotification *)notification\n{\n    self.downloadInfo = notification.object;\n    switch (self.downloadInfo.downloadState) {\n        case DYFStoreDownloadStateStarted:\n            DYFStoreLog(@\"The download started\");\n            break;\n        case DYFStoreDownloadStateInProgress:\n            DYFStoreLog(@\"The download progress: %.2f%%\", self.downloadInfo.downloadProgress);\n            break;\n        case DYFStoreDownloadStateCancelled:\n            DYFStoreLog(@\"The download cancelled\");\n            break;\n        case DYFStoreDownloadStateFailed:\n            DYFStoreLog(@\"The download failed\");\n            break;\n        case DYFStoreDownloadStateSucceeded:\n            DYFStoreLog(@\"The download succeeded: 100%%\");\n            break;\n        default:\n            break;\n    }\n}\n```\n\n### Receipt verification\n\n`DYFStoreKit` doesn't perform receipt verification by default, but provides reference implementations. You can implement your own custom verification or use the reference verifier provided by the library.\n\nThe reference verifier is outlined below. For more info, check out the [wiki](https://github.com/itenfay/DYFStoreKit/wiki/Receipt-verification).\n\n#### Reference verifier\n\nYou create and return a receipt verifier(`DYFStoreReceiptVerifier`) by using lazy loading.\n\n```\n- (DYFStoreReceiptVerifier *)receiptVerifier \n{\n    if (!_receiptVerifier) {\n        _receiptVerifier = [[DYFStoreReceiptVerifier alloc] init];\n        _receiptVerifier.delegate = self;\n    }\n    return _receiptVerifier;\n}\n```\n\nThe receipt verifier delegates receipt verification, enabling you to provide your own implementation using the `DYFStoreReceiptVerifierDelegate` protocol:\n\n```\n- (void)verifyReceiptDidFinish:(nonnull DYFStoreReceiptVerifier *)verifier didReceiveData:(nullable NSDictionary *)data {}\n\n- (void)verifyReceipt:(nonnull DYFStoreReceiptVerifier *)verifier didFailWithError:(nonnull NSError *)error {}\n```\n\nYou can start verifying the in-app purchase receipt. \n\n```\n// Fetches the data of the bundle’s App Store receipt. \nNSData *data = receiptData ?: [NSData dataWithContentsOfURL:DYFStore.receiptURL];\nDYFStoreLog(@\"data: %@\", data);\n\n[_receiptVerifier verifyReceipt:data];\n\n// Only used for receipts that contain auto-renewable subscriptions.\n//[_receiptVerifier verifyReceipt:data sharedSecret:@\"A43512564ACBEF687924646CAFEFBDCAEDF4155125657\"];\n```\n\nIf security is a concern you might want to avoid using an open source verification logic, and provide your own custom verifier instead.\n\nIt is better to use your own server to obtain the parameters uploaded from the client to verify the receipt from the app store server (C -\u003e Uploaded Parameters -\u003e S -\u003e App Store S -\u003e S -\u003e Receive And Parse Data -\u003e C, C: client, S: server).\n\n### Finish transactions\n\nThe transaction can be finished only after the client and server adopt secure communication and data encryption and the receipt verification is passed. In this way, we can avoid refreshing orders and cracking in-app purchase. If we were unable to complete the verification, we want `StoreKit` to keep reminding us that there are still outstanding transactions.\n\n```\n[DYFStore.defaultStore finishTransaction:transaction];\n```\n\n\n## Transaction persistence\n\n`DYFStoreKit` provides an optional reference implementation for storing transactions in `NSUserDefaults`(`DYFStoreUserDefaultsPersistence`). \n\nWhen the client crashes during the payment process, it is particularly important to store transaction information. When storekit notifies the uncompleted payment again, it takes the data directly from file and performs the receipt verification until the transaction is completed.\n\n### Store transaction\n\n```\n- (void)storeReceipt\n{\n    DYFStoreLog();\n    NSURL *receiptURL = DYFStore.receiptURL;\n    NSData *data = [NSData dataWithContentsOfURL:receiptURL];\n    if (!data || data.length == 0) {\n        [self refreshReceipt];\n        return;\n    }\n    \n    DYFStoreNotificationInfo *info = self.purchaseInfo;\n    DYFStoreUserDefaultsPersistence *persister = [[DYFStoreUserDefaultsPersistence alloc] init];\n    \n    DYFStoreTransaction *transaction = [[DYFStoreTransaction alloc] init];\n    if (info.state == DYFStorePurchaseStateSucceeded) {\n        transaction.state = DYFStoreTransactionStatePurchased;\n    } else if (info.state == DYFStorePurchaseStateRestored) {\n        transaction.state = DYFStoreTransactionStateRestored;\n    }\n    \n    transaction.productIdentifier = info.productIdentifier;\n    transaction.userIdentifier = info.userIdentifier;\n    transaction.transactionIdentifier = info.transactionIdentifier;\n    transaction.transactionTimestamp = info.transactionDate.timestamp;\n    transaction.originalTransactionTimestamp = info.originalTransactionDate.timestamp;\n    transaction.originalTransactionIdentifier = info.originalTransactionIdentifier;\n    \n    transaction.transactionReceipt = data.base64EncodedString;\n    [persister storeTransaction:transaction];\n    \n    [self verifyReceipt:data];\n}\n```\n\n### Remove transaction\n\n```\nDYFStoreNotificationInfo *info = self.purchaseInfo;\nDYFStore *store = DYFStore.defaultStore;\nDYFStoreUserDefaultsPersistence *persister = [[DYFStoreUserDefaultsPersistence alloc] init];\n\nif (info.state == DYFStorePurchaseStateRestored) {\n    SKPaymentTransaction *transaction = [store extractRestoredTransaction:info.transactionIdentifier];\n    [store finishTransaction:transaction];\n} else {\n    SKPaymentTransaction *transaction = [store extractPurchasedTransaction:info.transactionIdentifier];\n    // The transaction can be finished only after the client and server adopt secure communication and data encryption and the receipt verification is passed. In this way, we can avoid refreshing orders and cracking in-app purchase. If we were unable to complete the verification, we want `StoreKit` to keep reminding us that there are still outstanding transactions.\n    [store finishTransaction:transaction];\n}\n\n[persister removeTransaction:info.transactionIdentifier];\n\nif (info.originalTransactionIdentifier) {\n    [persister removeTransaction:info.originalTransactionIdentifier];\n}\n```\n\n\n## Requirements\n\n`DYFStoreKit` requires `iOS 7.0` or above and `ARC`.\n\n\n## Demo\n\nTo learn more, please clone this project (`git clone https://github.com/itenfay/DYFStoreKit.git`) to the local directory.\n\n\n## Feedback is welcome\n\nIf you notice any issue to create an issue. I will be happy to help you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitenfay%2Fdyfstorekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitenfay%2Fdyfstorekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitenfay%2Fdyfstorekit/lists"}