All Projects → dgynfi → DYFStoreKit

dgynfi / DYFStoreKit

Licence: other
([Swift] https://github.com/chenxing640/DYFStore) A lightweight and easy-to-use iOS library for In-App Purchases (Objective-C). DYFStoreKit uses blocks and notifications to wrap StoreKit, provides receipt verification and transaction persistence and doesn't require any external dependencies.

Programming Languages

objective c
16641 projects - #2 most used programming language
ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to DYFStoreKit

hms-iap-serverdemo
IAP server sample encapsulates APIs of the HUAWEI IAP server. It provides many sample programs for your reference or usage. The repository contains 8 branches of java, csharp, golang, nodejs, perl, php, python and ruby demo.
Stars: ✭ 20 (-61.54%)
Mutual labels:  pay, purchase, in-app-purchases
hms-iap-clientdemo-android-studio
This demo app provides all 3 types of product to demonstrate the procedure and capability of Huawei IAP.
Stars: ✭ 36 (-30.77%)
Mutual labels:  pay, purchase, in-app-purchase
sep-pay
Pay.ir Payment Package for Laravel 5.3+
Stars: ✭ 17 (-67.31%)
Mutual labels:  pay, payment
Ijpay
IJPay 让支付触手可及,封装了微信支付、QQ支付、支付宝支付、京东支付、银联支付、PayPal 支付等常用的支付方式以及各种常用的接口。不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。右上角点下小星星✨
Stars: ✭ 3,561 (+6748.08%)
Mutual labels:  pay, payment
Pay
个人网站即时到账收款解决方案 / Personal website instant payment solution
Stars: ✭ 558 (+973.08%)
Mutual labels:  pay, payment
Tpinappreceipt
Reading and Validating In App Purchase Receipt Locally.
Stars: ✭ 305 (+486.54%)
Mutual labels:  payment, purchase
PurchaseHelper
Sample app to explain the In-App purchase implementation in Android using Play-billing library
Stars: ✭ 30 (-42.31%)
Mutual labels:  purchase, in-app-purchase
Pay
可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了
Stars: ✭ 4,176 (+7930.77%)
Mutual labels:  pay, payment
DLInAppPurchase
A demo of In-App Purchase.苹果内购最详细教程
Stars: ✭ 37 (-28.85%)
Mutual labels:  purchase, in-app-purchase
Node Tenpay
微信支付 for nodejs
Stars: ✭ 947 (+1721.15%)
Mutual labels:  pay, payment
Laravel Pay
可能是我用过的最优雅的 Alipay 和 WeChat 的 laravel 支付扩展包了
Stars: ✭ 856 (+1546.15%)
Mutual labels:  pay, payment
credit-card-prompt
Credit card prompt with validation and address lookup
Stars: ✭ 13 (-75%)
Mutual labels:  pay, payment
bitcoincashjs
WARNING: This project is no longer maintained. Please, use bitcore-lib-cash instead.
Stars: ✭ 80 (+53.85%)
Mutual labels:  payment, transaction
Nativescript Purchase
💰 A NativeScript plugin for making in-app purchases!
Stars: ✭ 80 (+53.85%)
Mutual labels:  transaction, purchase
android-upi-payment
An android library for integrating payment using existing upi apps.
Stars: ✭ 26 (-50%)
Mutual labels:  payment, transaction
Gopay
golang支付:微信公众号,微信app,微信小程序,微信企业支付,支付宝网页版,支付宝app,支付宝企业支付
Stars: ✭ 313 (+501.92%)
Mutual labels:  pay, payment
Swiftystorekit
Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺
Stars: ✭ 5,796 (+11046.15%)
Mutual labels:  iap, in-app-purchase
Google-IAP
Android Library for easing Google Play Billing to your apps with support for Subscriptions, In-App Purchases and Consumables with a beautiful sample app.
Stars: ✭ 129 (+148.08%)
Mutual labels:  purchase, iap
Kirby Pay
Make online payments with Kirby
Stars: ✭ 27 (-48.08%)
Mutual labels:  pay, payment
cordova-plugin-purchases
Cordova in-app purchases and subscriptions made easy.
Stars: ✭ 52 (+0%)
Mutual labels:  iap, in-app-purchase

DYFStoreKit

A lightweight and easy-to-use iOS library for In-App Purchases. (Objective-C)

DYFStoreKit uses blocks and notifications to wrap StoreKit, provides receipt verification and transaction persistence. DYFStoreKit doesn't require any external dependencies.

License MIT  CocoaPods  CocoaPods 

Chinese Instructions (中文说明)

Related Links

Features

  • Super simple in-app purchases.
  • Built-in support for remembering your purchases.
  • Built-in receipt validation (remote).
  • Built-in hosted content downloads and notifications.

Group (ID:614799921)

Installation

Using CocoaPods:

pod 'DYFStoreKit', '~> 1.2.0'

Or

pod 'DYFStoreKit'

Check out the wiki for more options.

Usage

Next I'll show you how to use DYFStoreKit.

Initialization

The initialization is as follows.

  • Whether to allow the logs output to the console, set 'true' in debug mode, view the logs of the whole process of in-app purchase, and set 'false' when publishing app in release mode.
  • Adds the observer of transactions and monitors the change of transactions.
  • Instantiates data persistent object and stores the related information of transactions.
  • Follows the agreement DYFStoreAppStorePaymentDelegate and processes payments for products purchased from the App Store.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Adds an observer that responds to updated transactions to the payment queue.
    // 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.
    // 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.
    [DYFStore.defaultStore addPaymentTransactionObserver];

    // Sets the delegate processes the purchase which was initiated by user from the App Store.
    DYFStore.defaultStore.delegate = self;

    return YES;
}

You can process the purchase which was initiated by user from the App Store and provide your own implementation using the DYFStoreAppStorePaymentDelegate protocol:

// Processes the purchase which was initiated by user from the App Store.
- (void)didReceiveAppStorePurchaseRequest:(SKPaymentQueue *)queue payment:(SKPayment *)payment forProduct:(SKProduct *)product {
    
    if (![DYFStore canMakePayments]) {
        [self showTipsMessage:@"Your device is not able or allowed to make payments!"];
        return;
    }
    
    // Get account name from your own user system.
    NSString *accountName = @"Handsome Jon";
    
    // This algorithm is negotiated with server developer.
    NSString *userIdentifier = DYF_SHA256_HashValue(accountName);
    DYFStoreLog(@"userIdentifier: %@", userIdentifier);
    
    [DYFStore.defaultStore purchaseProduct:product.productIdentifier userIdentifier:userIdentifier];
}

Request products

You need to check whether the device is not able or allowed to make payments before requesting products.

if (![DYFStore canMakePayments]) {
    [self showTipsMessage:@"Your device is not able or allowed to make payments!"];
    return;
}

To 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.

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.

- (IBAction)fetchesProductAndSubmitsPayment:(id)sender {

    // You need to check whether the device is not able or allowed to make payments before requesting product.
    if (![DYFStore canMakePayments]) {
        [self showTipsMessage:@"Your device is not able or allowed to make payments!"];
        return;
    }
    
    [self showLoading:@"Loading..."];
    
    NSString *productId = @"com.hncs.szj.coin48";
    [DYFStore.defaultStore requestProductWithIdentifier:productId success:^(NSArray *products, NSArray *invalidIdentifiers) {
        
        [self hideLoading];
        
        if (products.count == 1) {
            
            NSString *productId = ((SKProduct *)products[0]).productIdentifier;
            [self addPayment:productId];
            
        } else {
            
            [self showTipsMessage:@"There is no this product for sale!"];
        }
        
    } failure:^(NSError *error) {
        
        [self hideLoading];
        
        NSString *value = error.userInfo[NSLocalizedDescriptionKey];
        NSString *msg = value ?: error.localizedDescription;
        // This indicates that the product cannot be fetched, because an error was reported.
        [self sendNotice:[NSString stringWithFormat:@"An error occurs, %zi, %@", error.code, msg]];
    }];
}

- (void)addPayment:(NSString *)productId {
    
    // Get account name from your own user system.
    NSString *accountName = @"Handsome Jon";
    
    // This algorithm is negotiated with server developer.
    NSString *userIdentifier = DYF_SHA256_HashValue(accountName);
    DYFStoreLog(@"userIdentifier: %@", userIdentifier);
    
    [DYFStore.defaultStore purchaseProduct:productId userIdentifier:userIdentifier];
}

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.

- (NSArray *)fetchProductIdentifiersFromServer {
    
    NSArray *productIds = @[@"com.hncs.szj.coin42",   // 42 gold coins for ¥6.
                            @"com.hncs.szj.coin210",  // 210 gold coins for ¥30.
                            @"com.hncs.szj.coin686",  // 686 gold coins for ¥98.
                            @"com.hncs.szj.coin1386", // 1386 gold coins for ¥198.
                            @"com.hncs.szj.coin2086", // 2086 gold coins for ¥298.
                            @"com.hncs.szj.coin4886", // 4886 gold coins for ¥698.
                            @"com.hncs.szj.vip1",     // non-renewable vip subscription for a month.
                            @"com.hncs.szj.vip2"      // Auto-renewable vip subscription for three months.
    ];
    
    return productIds;
}

- (IBAction)fetchesProductsFromAppStore:(id)sender {

    // You need to check whether the device is not able or allowed to make payments before requesting products.
    if (![DYFStore canMakePayments]) {
        [self showTipsMessage:@"Your device is not able or allowed to make payments!"];
        return;
    }

    [self showLoading:@"Loading..."];
    
    NSArray *productIds = [self fetchProductIdentifiersFromServer];
    [DYFStore.defaultStore requestProductWithIdentifiers:productIds success:^(NSArray *products, NSArray *invalidIdentifiers) {
        
        [self hideLoading];
        
        if (products.count > 0) {
            
            [self processData:products];
            
        } else if (products.count == 0 && invalidIdentifiers.count > 0) {
            
            // Please check the product information you set up.
            [self showTipsMessage:@"There are no products for sale!"];
        }
        
    } failure:^(NSError *error) {
        
        [self hideLoading];
        
        NSString *value = error.userInfo[NSLocalizedDescriptionKey];
        NSString *msg = value ?: error.localizedDescription;
        // This indicates that the products cannot be fetched, because an error was reported.
        [self sendNotice:[NSString stringWithFormat:@"An error occurs, %zi, %@", error.code, msg]];
    }];
}

- (void)processData:(NSArray *)products {
    
    NSMutableArray *modelArray = [NSMutableArray arrayWithCapacity:0];
    
    for (SKProduct *product in products) {
        
        DYFStoreProduct *p = [[DYFStoreProduct alloc] init];
        p.identifier = product.productIdentifier;
        p.name = product.localizedTitle;
        p.price = [product.price stringValue];
        p.localePrice = [DYFStore.defaultStore localizedPriceOfProduct:product];
        p.localizedDescription = product.localizedDescription;
        
        [modelArray addObject:p];
    }
    
    [self displayStoreUI:modelArray];
}

- (void)displayStoreUI:(NSMutableArray *)dataArray {
    
    DYFStoreViewController *storeVC = [[DYFStoreViewController alloc] init];
    storeVC.dataArray = dataArray;
    [self.navigationController pushViewController:storeVC animated:YES];
}

Add payment

Requests payment of the product with the given product identifier.

[DYFStore.defaultStore purchaseProduct:@"com.hncs.szj.coin210"];

If 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.

Calculates the SHA256 hash function:

CG_INLINE NSString *DYF_SHA256_HashValue(NSString *string) {

    const int digestLength = CC_SHA256_DIGEST_LENGTH; // 32
    unsigned char md[digestLength];

    const char *cStr = [string UTF8String];
    size_t cStrLen = strlen(cStr);

    // Confirm that the length of C string is small enough
    // to be recast when calling the hash function.
    if (cStrLen > UINT32_MAX) {
        NSLog(@"C string too long to hash: %@", string);
        return nil;
    }

    CC_SHA256(cStr, (CC_LONG)cStrLen, md);

    // Convert the array of bytes into a string showing its hex represention.
    NSMutableString *hash = [NSMutableString string];
    for (int i = 0; i < digestLength; i++) {

        // Add a dash every four bytes, for readability.
        if (i != 0 && i%4 == 0) {
            //[hash appendString:@"-"];
        }
        [hash appendFormat:@"%02x", md[i]];
    }

    return hash;
}

Requests payment of the product with the given product identifier, an opaque identifier for the user’s account on your system.

[DYFStore.defaultStore purchaseProduct:@"com.hncs.szj.coin210" userIdentifier:@"A43512564ACBEF687924646CAFEFBDCAEDF4155125657"];

Restore transactions

  • Restores transactions without the user account identifier.
[DYFStore.defaultStore restoreTransactions];
  • Restores transactions with the user account identifier.
[DYFStore.defaultStore restoreTransactions:@"A43512564ACBEF687924646CAFEFBDCAEDF4155125657"];

Refresh receipt

If Bundle.main.appStoreReceiptURL is null, you need to create a refresh receipt request to obtain a receipt for a payment transaction.

[DYFStore.defaultStore refreshReceiptOnSuccess:^{
    [self storeReceipt];
} failure:^(NSError *error) {
    [self failToRefreshReceipt];
}];

Notifications

DYFStoreKit sends notifications of StoreKit related events and extends NSNotification to provide relevant information. To receive them, add the observer to a DYFStoreKit manager.

Add the store observer

- (void)addStoreObserver {
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(processPurchaseNotification:) name:DYFStorePurchasedNotification object:nil];
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(processDownloadNotification:) name:DYFStoreDownloadedNotification object:nil];
}

Remove the store observer

When the application exits, you need to remove the store observer.

- (void)removeStoreObserver {
    [NSNotificationCenter.defaultCenter removeObserver:self name:DYFStorePurchasedNotification object:nil];
    [NSNotificationCenter.defaultCenter removeObserver:self name:DYFStoreDownloadedNotification object:nil];
}

Payment transaction notifications

Payment transaction notifications are sent after a payment has been requested or for each restored transaction.

- (void)processPurchaseNotification:(NSNotification *)notification {

    [self hideLoading];
    self.purchaseInfo = notification.object;

    switch (self.purchaseInfo.state) {
        case DYFStorePurchaseStatePurchasing:
            [self showLoading:@"Purchasing..."];
            break;
        case DYFStorePurchaseStateCancelled:
            [self sendNotice:@"You cancel the purchase"];
            break;
        case DYFStorePurchaseStateFailed:
            [self sendNotice:[NSString stringWithFormat:@"An error occurred, %zi", self.purchaseInfo.error.code]];
            break;
        case DYFStorePurchaseStateSucceeded:
        case DYFStorePurchaseStateRestored:
            [self completePayment];
            break;
        case DYFStorePurchaseStateRestoreFailed:
            [self sendNotice:[NSString stringWithFormat:@"An error occurred, %zi", self.purchaseInfo.error.code]];
            break;
        case DYFStorePurchaseStateDeferred:
            DYFStoreLog(@"Deferred");
            break;
        default:
            break;
    }
}

Download notifications

- (void)processDownloadNotification:(NSNotification *)notification {

    self.downloadInfo = notification.object;

    switch (self.downloadInfo.downloadState) {
        case DYFStoreDownloadStateStarted:
            DYFStoreLog(@"The download started");
            break;
        case DYFStoreDownloadStateInProgress:
            DYFStoreLog(@"The download progress: %.2f%%", self.downloadInfo.downloadProgress);
            break;
        case DYFStoreDownloadStateCancelled:
            DYFStoreLog(@"The download cancelled");
            break;
        case DYFStoreDownloadStateFailed:
            DYFStoreLog(@"The download failed");
            break;
        case DYFStoreDownloadStateSucceeded:
            DYFStoreLog(@"The download succeeded: 100%%");
            break;
        default:
            break;
    }
}

Receipt verification

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.

The reference verifier is outlined below. For more info, check out the wiki.

Reference verifier

You create and return a receipt verifier(DYFStoreReceiptVerifier) by using lazy loading.

- (DYFStoreReceiptVerifier *)receiptVerifier {
    if (!_receiptVerifier) {
        _receiptVerifier = [[DYFStoreReceiptVerifier alloc] init];
        _receiptVerifier.delegate = self;
    }
    return _receiptVerifier;
}

The receipt verifier delegates receipt verification, enabling you to provide your own implementation using the DYFStoreReceiptVerifierDelegate protocol:

- (void)verifyReceiptDidFinish:(nonnull DYFStoreReceiptVerifier *)verifier didReceiveData:(nullable NSDictionary *)data;

- (void)verifyReceipt:(nonnull DYFStoreReceiptVerifier *)verifier didFailWithError:(nonnull NSError *)error;

You can start verifying the in-app purchase receipt.

// Fetches the data of the bundle’s App Store receipt. 
NSData *data = receiptData ?: [NSData dataWithContentsOfURL:DYFStore.receiptURL];
DYFStoreLog(@"data: %@", data);

[_receiptVerifier verifyReceipt:data];

// Only used for receipts that contain auto-renewable subscriptions.
//[_receiptVerifier verifyReceipt:data sharedSecret:@"A43512564ACBEF687924646CAFEFBDCAEDF4155125657"];

If security is a concern you might want to avoid using an open source verification logic, and provide your own custom verifier instead.

It 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 -> Uploaded Parameters -> S -> App Store S -> S -> Receive And Parse Data -> C, C: client, S: server).

Finish transactions

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.

[DYFStore.defaultStore finishTransaction:transaction];

Transaction persistence

DYFStoreKit provides an optional reference implementation for storing transactions in NSUserDefaults(DYFStoreUserDefaultsPersistence).

When 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.

Store transaction

- (void)storeReceipt {
    DYFStoreLog();
    
    NSURL *receiptURL = DYFStore.receiptURL;
    NSData *data = [NSData dataWithContentsOfURL:receiptURL];
    if (!data || data.length == 0) {
        [self refreshReceipt];
        return;
    }
    
    DYFStoreNotificationInfo *info = self.purchaseInfo;
    DYFStoreUserDefaultsPersistence *persister = [[DYFStoreUserDefaultsPersistence alloc] init];
    
    DYFStoreTransaction *transaction = [[DYFStoreTransaction alloc] init];
    
    if (info.state == DYFStorePurchaseStateSucceeded) {
        transaction.state = DYFStoreTransactionStatePurchased;
    } else if (info.state == DYFStorePurchaseStateRestored) {
        transaction.state = DYFStoreTransactionStateRestored;
    }
    
    transaction.productIdentifier = info.productIdentifier;
    transaction.userIdentifier = info.userIdentifier;
    transaction.transactionIdentifier = info.transactionIdentifier;
    transaction.transactionTimestamp = info.transactionDate.timestamp;
    transaction.originalTransactionTimestamp = info.originalTransactionDate.timestamp;
    transaction.originalTransactionIdentifier = info.originalTransactionIdentifier;
    
    transaction.transactionReceipt = data.base64EncodedString;
    [persister storeTransaction:transaction];
    
    [self verifyReceipt:data];
}

Remove transaction

DYFStoreNotificationInfo *info = self.purchaseInfo;
DYFStore *store = DYFStore.defaultStore;
DYFStoreUserDefaultsPersistence *persister = [[DYFStoreUserDefaultsPersistence alloc] init];

if (info.state == DYFStorePurchaseStateRestored) {
    
    SKPaymentTransaction *transaction = [store extractRestoredTransaction:info.transactionIdentifier];
    [store finishTransaction:transaction];
    
} else {
    
    SKPaymentTransaction *transaction = [store extractPurchasedTransaction:info.transactionIdentifier];
    // 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.
    [store finishTransaction:transaction];
}

[persister removeTransaction:info.transactionIdentifier];
if (info.originalTransactionIdentifier) {
    [persister removeTransaction:info.originalTransactionIdentifier];
}

Requirements

DYFStoreKit requires iOS 7.0 or above and ARC.

Demo

To learn more, please clone this project (git clone https://github.com/dgynfi/DYFStoreKit.git) to the local directory.

Feedback is welcome

If you notice any issue, got stuck or just want to chat feel free to create an issue. I will be happy to help you.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].