Skip to content

Instantly share code, notes, and snippets.

@speaktoalvin
Last active April 29, 2021 04:07
Show Gist options
  • Save speaktoalvin/7c61bab7a401e22ee8c5 to your computer and use it in GitHub Desktop.
Save speaktoalvin/7c61bab7a401e22ee8c5 to your computer and use it in GitHub Desktop.
In App Purchase in Swift, with Receipt Validation
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)
}
}
}
}
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.
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);
?>
// 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
}
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
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.
@xkenpachi
Copy link

Hi,

Where do i need to call the function : validateReceipt ?
I mean in viewDidload?

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment