Last active
April 29, 2021 04:07
-
-
Save speaktoalvin/7c61bab7a401e22ee8c5 to your computer and use it in GitHub Desktop.
In App Purchase in Swift, with Receipt Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import StoreKit | |
//MARK: SKProductsRequestDelegate | |
extension IAPHelpers : SKProductsRequestDelegate | |
{ | |
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) | |
{ | |
if response.products.count > 0 | |
{ | |
let validProduct : SKProduct = response.products[0] | |
switch validProduct.productIdentifier { | |
case PRODUCT_IDENTIFIERS.k_10_WORDS_PRODUCT_IDENTIFIER.rawValue : | |
self.buyProduct(validProduct) | |
case PRODUCT_IDENTIFIERS.k_20_WORDS_PRODUCT_IDENTIFIER.rawValue : | |
self.buyProduct(validProduct) | |
case PRODUCT_IDENTIFIERS.k_ALL_WORDS_PRODUCT_IDENTIFIER.rawValue : | |
self.buyProduct(validProduct) | |
default : | |
self.alertThis("Sorry", message: "No products recieved from server", current: self.currentViewController) | |
} | |
} else { | |
self.alertThis("Sorry", message: "Can't make payments, Please check in settings.", current: self.currentViewController) | |
} | |
} | |
} | |
extension IAPHelpers : SKRequestDelegate | |
{ | |
func requestDidFinish(request: SKRequest) | |
{ | |
} | |
func request(request: SKRequest, didFailWithError error: NSError) | |
{ | |
self.alertThis("Sorry", message: "\(error.localizedDescription)", current: self.currentViewController) | |
} | |
} | |
//MARK: SKPaymentTransactionObserver | |
extension IAPHelpers : SKPaymentTransactionObserver | |
{ | |
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) | |
{ | |
self.handlingTransactions(transactions) | |
} | |
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) | |
{ | |
if queue.transactions.count == 0 | |
{ | |
self.alertThis("Sorry", message: "You didnt make any purchase to restiore, Please do a purchase first.", current: self.currentViewController) | |
} | |
else | |
{ | |
self.handlingTransactions(queue.transactions) | |
} | |
} | |
func paymentQueue(queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) | |
{ | |
self.alertThis("Sorry", message: "\(error.localizedDescription)", current: self.currentViewController) | |
} | |
} | |
enum PRODUCT_IDENTIFIERS : String | |
{ | |
//MARK: Product ID's | |
case k_10_WORDS_PRODUCT_IDENTIFIER = "YOUR_PRODUCT_IDENTIFIER_10" | |
case k_20_WORDS_PRODUCT_IDENTIFIER = "YOUR_PRODUCT_IDENTIFIER_20" | |
case k_ALL_WORDS_PRODUCT_IDENTIFIER = "YOUR_PRODUCT_IDENTIFIER_ALL" | |
case k_RESTORE_WORDS_PRODUCT_IDENTIFIER = "YOUR_PRODUCT_IDENTIFIER_RESTOREE" | |
} | |
enum USER_DEFAULTS_IDENTIFIERS : String | |
{ | |
case TEN_WORDS_PRODUCT_IDENTIFIER = "10_WORDS_IDENTIFIER" | |
case TWENTY_WORDS_PRODUCT_IDENTIFIER = "20_WORDS_IDENTIFIER" | |
case ALL_WORDS_PRODUCT_IDENTIFIER = "ALL_WORDS_IDENTIFIER" | |
} | |
enum ReceiptURL : String | |
{ | |
case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt" | |
case production = "https://buy.itunes.apple.com/verifyReceipt" | |
case myServer = "your server" | |
} | |
protocol StoreRequestIAPPorotocol | |
{ | |
func transactionCompletedForRequest(PRODUCT_ID : String) | |
} | |
class IAPHelpers: NSObject { | |
//MARK: Variables | |
var delegate : StoreRequestIAPPorotocol! | |
var currentViewController : UIViewController! | |
//MARK: - Creating sharedInstance | |
class var sharedInstance: IAPHelpers { | |
struct Static { | |
static var sharedInstance: IAPHelpers? | |
static var token: dispatch_once_t = 0 | |
} | |
dispatch_once(&Static.token) { | |
Static.sharedInstance = IAPHelpers() | |
} | |
return Static.sharedInstance! | |
} | |
//MARK: Product Request | |
func productRquestStarted(productReferenceName : String) | |
{ | |
if (SKPaymentQueue.canMakePayments()) { | |
if productReferenceName == PRODUCT_IDENTIFIERS.k_RESTORE_WORDS_PRODUCT_IDENTIFIER.rawValue | |
{ | |
self.restorePurchases() | |
} | |
else | |
{ | |
let productID : Set<String> = [productReferenceName] | |
let productsRequest : SKProductsRequest = SKProductsRequest(productIdentifiers: productID) | |
productsRequest.delegate = self | |
productsRequest.start() | |
} | |
} else { | |
// PRESENT A USER FOR UI about not being able to make payments. | |
} | |
} | |
//MARK: Buy Product - Payment Section | |
func buyProduct(product : SKProduct) | |
{ | |
let payment = SKPayment(product: product) | |
SKPaymentQueue.defaultQueue().addTransactionObserver(self) | |
SKPaymentQueue.defaultQueue().addPayment(payment) | |
} | |
//MARK: Restore Transaction | |
func restoreTransaction(transaction : SKPaymentTransaction) | |
{ | |
self.deliverProduct(transaction.payment.productIdentifier) | |
} | |
//MARK: Restore Purchases | |
func restorePurchases() | |
{ | |
SKPaymentQueue.defaultQueue().addTransactionObserver(self) | |
SKPaymentQueue.defaultQueue().restoreCompletedTransactions() | |
} | |
//MARK: - Showing UIAlertView | |
func alertThis(title : String, message : String, current : UIViewController) | |
{ | |
let alertView : UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) | |
alertView.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.Default, handler: { _ in | |
})) | |
current.presentViewController(alertView, animated: true, completion: nil) | |
} | |
//MARK: Transaction Observer Handler | |
func handlingTransactions(transactions : [AnyObject]) | |
{ | |
for transaction in transactions { | |
if let paymentTransaction : SKPaymentTransaction = transaction as? SKPaymentTransaction { | |
switch paymentTransaction.transactionState { | |
case .Purchasing : | |
print("Purchasing") | |
case .Purchased : | |
self.deliverProduct(paymentTransaction.payment.productIdentifier) | |
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction) | |
break | |
case .Failed: | |
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction) | |
break | |
case .Restored: | |
print("Restored") | |
self.restoreTransaction(paymentTransaction) | |
break | |
default: | |
print("DEFAULT") | |
// PRESENT A USER FOR UI about not being able to make payments. | |
break | |
} | |
} | |
} | |
} | |
//MARK: Deliver Product | |
func deliverProduct(product : String) | |
{ | |
self.validateReceipt { status in | |
if status | |
{ | |
self.delegate.transactionCompletedForRequest(product) | |
} | |
else | |
{ | |
print("Something bad happened") | |
} | |
} | |
} | |
//MARK: Receipt Validation | |
func validateReceipt(completion : (status : Bool) -> ()) { | |
let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL | |
let receipt: NSData = NSData(contentsOfURL: receiptUrl!)! | |
let receiptdata: NSString = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) | |
let dict = ["receipt-data" : receiptdata] | |
let jsonData = try! NSJSONSerialization.dataWithJSONObject(dict, options: NSJSONWritingOptions(rawValue: 0)) | |
let request = NSMutableURLRequest(URL: NSURL(string: ReceiptURL.sandbox.rawValue)!) | |
let session = NSURLSession.sharedSession() | |
request.HTTPMethod = "POST" | |
request.HTTPBody = jsonData | |
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error in | |
if let dataR = data | |
{ | |
self.handleData(dataR, completion: { status in | |
completion(status: status) | |
}) | |
} | |
}) | |
task.resume() | |
} | |
func handleData(responseDatas : NSData, completion : (status : Bool) -> ()) | |
{ | |
if let json = try! NSJSONSerialization.JSONObjectWithData(responseDatas, options: NSJSONReadingOptions.MutableLeaves) as? NSDictionary | |
{ | |
if let value = json.valueForKeyPath("status") as? Int | |
{ | |
if value == 0 | |
{ | |
completion(status: true) | |
} | |
else | |
{ | |
completion(status: false) | |
} | |
} | |
else | |
{ | |
completion(status: false) | |
} | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
About Receipt Validation | |
After you have processed your In-App Purchases you may want to verify the receipt with the App Store directly. You’ll get more details about the purchase so you can store that information in your database. There are two ways: first connecting to the Apple App Store directly within your app, and second send the receipt data to your own server and having your server perform the validation with the App Store server. The second is the easiest and most secure method when verifying receipts since it protects against Man-in-the-middle (MITM) attacks where its possible to get free in-app purchases in any app. | |
Apple recommends you use a trusted server to communicate with the App Store. Using your own server lets you design your app to recognize and trust only your server, and lets you ensure that your server connects with the App Store server. It is not possible to build a trusted connection between a user’s device and the App Store directly because you don’t control either end of that connection. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PHP Server Side Code | |
<?php | |
function getReceiptData($receipt) | |
{ | |
$fh = fopen('showme.txt',w); | |
fwrite($fh,$receipt); | |
fclose($fh); | |
$endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; | |
$ch = curl_init($endpoint); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, $receipt); | |
$response = curl_exec($ch); | |
$errno = curl_errno($ch); | |
$errmsg = curl_error($ch); | |
curl_close($ch); | |
$msg = $response.' - '.$errno.' - '.$errmsg; | |
echo $response; | |
} | |
foreach ($_POST as $key=>$value){ | |
$newcontent .= $key.' '.$value; | |
} | |
$new = trim($newcontent); | |
$new = trim($newcontent); | |
$new = str_replace('_','+',$new); | |
$new = str_replace(' =','==',$new); | |
if (substr_count($new,'=') == 0){ | |
if (strpos('=',$new) === false){ | |
$new .= '='; | |
} | |
} | |
$new = '{"receipt-data":"'.$new.'","password":"<INSERT YOUR IN-APP PURCHASE SHARED SECRET HERE>"}'; | |
$info = getReceiptData($new); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Receipt Example | |
{ | |
"receipt": { | |
"original_purchase_date_pst": "2012-04-30 08:05:55 America/Los_Angeles", | |
"original_transaction_id": "1000000046178817", | |
"original_purchase_date_ms": "1335798355868", | |
"transaction_id": "1000000046178817", | |
"quantity": "1", | |
"product_id": "br.com.jera.Example", | |
"bvrs": "20120427", | |
"purchase_date_ms": "1335798355868", | |
"purchase_date": "2012-04-30 15:05:55 Etc/GMT", | |
"original_purchase_date": "2012-04-30 15:05:55 Etc/GMT", | |
"purchase_date_pst": "2012-04-30 08:05:55 America/Los_Angeles", | |
"bid": "br.com.jera.Example", | |
"item_id": "521129812" | |
}, | |
"status": 0 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Ruby Server Side Code | |
# This code call the apple sandbox in app purchase server to validate a receipt using Ruby Code. | |
# Run this using "ruby verifyReceipt.rb" | |
# By @sauloarruda (http://twitter.com/sauloarruda) | |
require 'net/http' | |
# This core reads an file called receipt (see an example bellow) | |
params_json = "{ \"receipt-data\": \"#{open("./receipt").read}\" }" | |
# Use net/http to post to apple sandbox server | |
uri = URI("https://sandbox.itunes.apple.com") # Use "https://buy.itunes.apple.com" for production | |
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http| | |
response = http.post('/verifyReceipt', params_json) | |
# Puts the result! (see an example below - result.json) | |
puts response.body | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Status Codes | |
21000 - The App Store could not read the JSON object you provided. | |
21002 - The data in the receipt-data property was malformed or missing. | |
21003 - The receipt could not be authenticated. | |
21004 - The shared secret you provided does not match the shared secret on file for your account. | |
Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. | |
21005 - The receipt server is not currently available. | |
21006 - This receipt is valid but the subscription has expired. When this status code is | |
returned to your server, the receipt data is also decoded and returned as part of the response. | |
Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. | |
21007 - This receipt is from the test environment, but it was sent to the production environment for verification. | |
Send it to the test environment instead. | |
21008 - This receipt is from the production environment, but it was sent to the test environment for verification. | |
Send it to the production environment instead. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,
Where do i need to call the function : validateReceipt ?
I mean in viewDidload?
thanks