An open API service indexing awesome lists of open source software.

https://github.com/itenfay/dyfstore

iOS内购(This is used for in app purchases on iOS)
https://github.com/itenfay/dyfstore

buy iap in-app-purchase in-app-purchases ios pay payment purchase receipt receipt-url receipt-validation receipt-verification receipt-verifier skpayment skpaymentqueue skpaymenttransaction skproduct storekit swift transaction

Last synced: 19 days ago
JSON representation

iOS内购(This is used for in app purchases on iOS)

Awesome Lists containing this project

README

        

[中文版](README.md) | **English Version**

## DYFStore

A lightweight and easy-to-use iOS library for In-App Purchases ([Objective-C Version](https://github.com/itenfay/DYFStoreKit)).

`DYFStore` uses blocks and [notifications](#Notifications) to wrap `StoreKit`, provides [receipt verification](#Receipt-verification) and [transaction persistence](#Transaction-persistence).

[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](LICENSE) 
[![CocoaPods](http://img.shields.io/cocoapods/v/DYFStore.svg?style=flat)](http://cocoapods.org/pods/DYFStore) 
![CocoaPods](http://img.shields.io/cocoapods/p/DYFStore.svg?style=flat) 

## Related Links

- [DYFSwiftRuntimeProvider](https://github.com/itenfay/DYFSwiftRuntimeProvider/)
- [DYFSwiftKeychain](https://github.com/itenfay/DYFSwiftKeychain/)
- [DYFStoreReceiptVerifier_Swift](https://github.com/itenfay/DYFStoreReceiptVerifier_Swift/)
- [Unity-iOS-InAppPurchase](https://github.com/itenfay/Unity-iOS-InAppPurchase/)
- [in-app-purchase-complete-programming-guide-for-iOS](https://itenfay.github.io/2016/10/16/in-app-purchase-complete-programming-guide-for-iOS/)
- [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/)

## 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](https://cocoapods.org):

```
pod 'DYFStore'
```

Or

```
pod 'DYFStore', '~> 2.3.0'
```

Check out the [wiki](https://github.com/itenfay/DYFStore/wiki/Installation) for more options.

## Usage

Next I'll show you how to use `DYFStore`.

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

```
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

self.initIAPSDK()

return true
}

func initIAPSDK() {
SKIAPManager.shared.addStoreObserver()

// Wether to allow the logs output to console.
DYFStore.default.enableLog = true

// 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.default.addPaymentTransactionObserver()

// Sets the delegate processes the purchase which was initiated by user from the App Store.
DYFStore.default.delegate = self
}
```

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.
func didReceiveAppStorePurchaseRequest(_ queue: SKPaymentQueue, payment: SKPayment, forProduct product: SKProduct) -> Bool {
if !DYFStore.canMakePayments() {
self.sk_showTipsMessage("Your device is not able or allowed to make payments!")
return false
}

// Get account name from your own user system.
let accountName = "Handsome Jon"
// This algorithm is negotiated with server developer.
let userIdentifier = accountName.tx_sha256 ?? ""
DYFStoreLog("userIdentifier: \(userIdentifier)")
SKIAPManager.shared.addPayment(product.productIdentifier, userIdentifier: userIdentifier)
return true
}
```

### Request products

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

```
if !DYFStore.canMakePayments() {
self.sk_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 func fetchesProductAndSubmitsPayment(_ sender: Any) {
// You need to check whether the device is not able or allowed to make payments before requesting product.
if !DYFStore.canMakePayments() {
self.sk_showTipsMessage("Your device is not able or allowed to make payments!")
return
}
self.sk_showLoading("Loading...")

let productId = "com.hncs.szj.coin42"
DYFStore.default.requestProduct(withIdentifier: productId, success: { (products, invalidIdentifiers) in
self.sk_hideLoading()
if products.count == 1 {
let productId = products[0].productIdentifier
self.addPayment(productId)
} else {
self.sk_showTipsMessage("There is no this product for sale!")
}
}) { (error) in
self.sk_hideLoading()
let value = error.userInfo[NSLocalizedDescriptionKey] as? String
let msg = value ?? "\(error.localizedDescription)"
self.sendNotice("An error occurs, \(error.code), " + msg)
}
}

private func addPayment(_ productId: String) {
// Get account name from your own user system.
let accountName = "Handsome Jon"
// This algorithm is negotiated with server developer.
let userIdentifier = accountName.tx_sha256 ?? ""
DYFStoreLog("userIdentifier: \(userIdentifier)")
SKIAPManager.shared.addPayment(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.

```
func fetchProductIdentifiersFromServer() -> [String] {
let 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 func fetchesProductsFromAppStore(_ sender: Any) {
// You need to check whether the device is not able or allowed to make payments before requesting products.
if !DYFStore.canMakePayments() {
self.sk_showTipsMessage("Your device is not able or allowed to make payments!")
return
}
self.sk_showLoading("Loading...")

let productIds = fetchProductIdentifiersFromServer()
DYFStore.default.requestProduct(withIdentifiers: productIds, success: { (products, invalidIdentifiers) in
self.sk_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.sk_showTipsMessage("There are no products for sale!")
}
}) { (error) in
self.sk_hideLoading()
let value = error.userInfo[NSLocalizedDescriptionKey] as? String
let msg = value ?? "\(error.localizedDescription)"
self.sendNotice("An error occurs, \(error.code), " + msg)
}
}

private func processData(_ products: [SKProduct]) {
var modelArray = [SKStoreProduct]()
for product in products {
let p = SKStoreProduct()
p.identifier = product.productIdentifier
p.name = product.localizedTitle
p.price = product.price.stringValue
p.localePrice = DYFStore.default.localizedPrice(ofProduct: product)
p.localizedDescription = product.localizedDescription
modelArray.append(p)
}
self.displayStoreUI(modelArray)
}

private func displayStoreUI(_ dataArray: [SKStoreProduct]) {
let storeVC = DYFStoreViewController()
storeVC.dataArray = dataArray
self.navigationController?.pushViewController(storeVC, animated: true)
}
```

### Add payment

Requests payment of the product with the given product identifier.

```
DYFStore.default.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:

```
extension String {
/// Custom method to calculate the SHA-256 hash using Common Crypto.
///
/// - Parameter s: A string to calculate hash.
/// - Returns: A SHA-256 hash value string.
public var tx_sha256 : String? {
guard let cStr = cString(using: String.Encoding.utf8) else {
return nil
}
let digestLength = Int(CC_SHA256_DIGEST_LENGTH) // 32
let cStrLen = Int(lengthOfBytes(using: String.Encoding.utf8))

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

let md = UnsafeMutablePointer.allocate(capacity: digestLength)
CC_SHA256(cStr, CC_LONG(cStrLen), md)
// Convert the array of bytes into a string showing its hex represention.
let hash = NSMutableString()
for i in 0.. 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.default.finishTransaction(transaction)
```

## Transaction persistence

`DYFStore` 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 keychain and performs the receipt verification until the transaction is completed.

### Store transaction

```
func storeReceipt() {
guard let url = DYFStore.receiptURL() else {
self.refreshReceipt()
return
}
do {
let data = try Data(contentsOf: url)
let info = self.purchaseInfo!
let persister = DYFStoreUserDefaultsPersistence()

let tx = DYFStoreTransaction()
if info.state! == .succeeded {
tx.state = DYFStoreTransactionState.purchased.rawValue
} else if info.state! == .restored {
tx.state = DYFStoreTransactionState.restored.rawValue
}

tx.productIdentifier = info.productIdentifier
tx.userIdentifier = info.userIdentifier
tx.transactionTimestamp = info.transactionDate?.timestamp()
tx.transactionIdentifier = info.transactionIdentifier
tx.originalTransactionTimestamp = info.originalTransactionDate?.timestamp()
tx.originalTransactionIdentifier = info.originalTransactionIdentifier

tx.transactionReceipt = data.base64EncodedString()
persister.storeTransaction(tx)

self.verifyReceipt(data)
} catch let error {
DYFStoreLog("error: \(error.localizedDescription)")
self.refreshReceipt()
return
}
}
```

### Remove transaction

```
let info = self.purchaseInfo!
let store = DYFStore.default
let persister = DYFStoreUserDefaultsPersistence()
let identifier = info.transactionIdentifier!

if info.state! == .restored {
let tx = store.extractRestoredTransaction(identifier)
store.finishTransaction(tx)
} else {
let tx = store.extractPurchasedTransaction(identifier)
// The transaction can be finished only after the receipt verification passed under the client and the server can adopt the communication of security and data encryption. 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 of the transaction.
store.finishTransaction(tx)
}

persister.removeTransaction(identifier)

if let id = info.originalTransactionIdentifier {
persister.removeTransaction(id)
}
```

## Requirements

`DYFStore` requires `iOS 8.0` or above and `ARC`.

## Demo

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

## Feedback is welcome

If you notice any issue to create an issue. I will be happy to help you.