on
Italy
- Get link
- X
- Other Apps
Note: This article assumes that you have an iOS app and sufficient knowledge on how to integrate with in-app purchases. I’ll be focusing only on how to manage it from your Rails app and won’t show any ObjectiveC code examples.We will be using the app that we built in my previous article on Stripe Subscriptions as a base. It uses MongoDB for the database, but the examples here should work with ActiveRecord with just some minor changes. With that out of the way, let us begin.
# routes.rb
# IOS subscription
post '/receipt_validate/' => 'checkouts#handle_ios_transaction'
In app/controllers/checkouts_controller:# checkouts_controller.rb
def handle_ios_transaction
item_id = params[:item_id]
item = Items.find(item_id.to_s)
if Rails.env == 'development' or Rails.env == 'test' or Rails.env == 'staging'
apple_receipt_verify_url = "https://sandbox.itunes.apple.com/verifyReceipt"
else
#apple_receipt_verify_url = "https://sandbox.itunes.apple.com/verifyReceipt"
apple_receipt_verify_url = "https://buy.itunes.apple.com/verifyReceipt"
end
url = URI.parse(apple_receipt_verify_url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
valid = false
json_request = {'receipt-data' => params[:receipt_data] }.to_json
resp = http.post(url.path, json_request, {'Content-Type' => 'application/x-www-form-urlencoded'})
resp_body = resp
json_resp = JSON.parse(resp_body.body)
if resp.code == '200'
if json_resp['status'] == 0
valid = true
current_IAP_receipt = json_resp['receipt']['in_app'].find {|x| x['product_id'] == item_id}
respond_to do |format|
format.json { render json: {message: "purchase successful!"} and return }
end
else
Rails.logger.info("Apple_#{Rails.env} json_resp for verify_itunes #{json_resp['status']}")
respond_to do |format|
format.json { render json: {status: "invalid", errorCode: "#{json_resp['status']}"} and return }
end
end
else
format.json { render json: {status: "invalid", resp: "#{resp.code}"} and return }
end
end
In config/environments/development.rb:ITUNES = {:receipt_url => "https://sandbox.itunes.apple.com/verifyReceipt"}
Finally, in config/environments/production.rb:ITUNES = {:receipt_url => "https://buy.itunes.apple.com/verifyReceipt"}
Okay, that’s some verbose code. Don’t worry if you don’t get it at the first glance, I will walk you through it.url = URI.parse(apple_receipt_verify_url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
valid = false
json_request = {'receipt-data' =>params[:receipt_data] }.to_json
resp = http.post(url.path, json_request, {'Content-Type' => 'application/x-www-form-urlencoded'})
resp_body = resp
json_resp = JSON.parse(resp_body.body)
Then we’re using Ruby’s HTTP module to send a POST request to Apple’s
servers with the receipt data. If the payment is successful Apple will
respond with a status code of 0. The detailed list of status codes is available in the developer docs.Note: The receipt data is base64 encoded.If the receipt is valid, then save the receipt data into our database, for future use. Add a new model for ITunes receipts:
# models/itunes_receipt.rb
class ItunesReceipt
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
include Mongoid::Attributes::Dynamic
field :original_purchase_date_pst, type: String
field :purchase_date_ms, type: String
field :unique_identifier, type: String
field :original_transaction_id, type: String
field :bvrs, type: String
field :transaction_id, type: String
field :quantity, type: String
field :unique_vendor_identifier, type: String
field :item_id, type: String
field :product_id, type: String
field :purchase_date, type: String
field :original_purchase_date, type: String
field :purchase_date_pst, type: String
field :bid, type: String
field :original_purchase_date_ms, type: String
field :status, type: String
end
Save the following in app/controllers/checkouts_controller.rb:def save_receipt(receipt_data)
receipt = Itunesreceipt.new
receipt.original_purchase_date_pst = receipt_data['original_purchase_date_pst']
receipt.purchase_date_ms = receipt_data['purchase_date_ms']
receipt.original_transaction_id = receipt_data['original_transaction_id']
receipt.transaction_id = receipt_data['transaction_id']
receipt.quantity = receipt_data['quantity']
receipt.product_id = receipt_data['product_id']
receipt.purchase_date = receipt_data['purchase_date']
receipt.original_purchase_date = receipt_data['original_purchase_date']
receipt.purchase_date_pst = receipt_data['purchase_date_pst']
receipt.original_purchase_date_ms = receipt_data['original_purchase_date_ms']
receipt.save
end
Wait, how will you share this purchase with my website? Pass the user
id with the validation request, if available. With that, we can
associate the purchase with the user.# checkouts_controller.rb
def handle_ios_transaction
user = User.find(params[:user_id])
#...
if json_resp['status'] == 0
valid = true
current_IAP_receipt = json_resp['receipt']['in_app'].find {|x| x['product_id'] == item_id}
if user
current_IAP_receipt.user_id = user.id
current_IAP_receipt.save
end
respond_to do |format|
format.json { render json: {message: "purchase successful!"} and return }
end
#...
end
Set the relationship in itunes_receipt.rb:class ItunesReceipt
#....
belongs_to :user
#....
end
class ItunesReceipt
#..........
field :expiry_date_ms, type: String
#..........
def is_expired?
Time.now.to_i > self.expired_date_ms.to_i
end
end
Non-renewable subscriptions work pretty similarly to In-app purchases. Just save the expiry_date in your receipt model and validate against that every time the content is accessed.class IosSubscription
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
include Mongoid::Attributes::Dynamic
#Fields from Apple
field :original_purchase_date, type: String
field :original_purchase_date_pst, type: String
field :original_purchase_date_ms, type: String
field :product_id, type: String
field :is_trial_period, type: Boolean
field :purchase_date, type: String
field :purchase_date_pst, type: String
field :purchase_date_ms, type: String
field :expires_date, type: String
field :expires_date_ms, type: String
field :expires_date_pst, type: String
field :original_transaction_id, type: String
field :transaction_id, type: String
field :web_order_line_item_id, type: String
field :quantity, type: Integer
field :receipt_data, type: String
#Flag for production/test receipts
field :mode, type: String
field :udid, type: String
belongs_to :user
end
and in your checkouts_controller.rb, add: def verify_subscription_ios
user_id = params[:user_id]
@UDID = params[:udid]
@user = User.find(user_id) unless user_id.nil?
@source = "ipad"
@shared_secret = ITUNES[:secret]
@amount = params[:amount]
@apple_receipt_verify_url = ITUNES[:receipt_url]
json_resp = validate_receipt(@apple_receipt_verify_url)
if @resp.code == '200' and json_resp['status'] == 0
save_receipt(json_resp)
else
respond_to do |format|
format.json { render json: {status: "invalid", mode: @mode, errorCode: "#{json_resp['status']}"} and return }
end
end
end
private
# Persist the receipt data in production server
def save_receipt(json_resp)
@latest_receipt = json_resp['latest_receipt_info'].last
#Generate the receipt
create_receipt
@valid = true
respond_to do |format|
format.json { render json: {status: true, message: "purchase successful!", mode: @mode, expires_at: DateTime.parse(@ios_subscription.expires_date_pst).strftime('%m/%d/%Y')} and return }
end
end
#Create receipt only if needed
def create_receipt
log "Checking subscription receipt"
# If the original transaction id is the same as the current transaction id then this is a new record
@ios_subscription = IosSubscription.find_by(:original_transaction_id => @latest_receipt['original_transaction_id'])
if @ios_subscription.nil? and @latest_receipt['original_transaction_id'] == @latest_receipt['transaction_id']
new_receipt()
else
unless @ios_subscription.nil?
#Update Existing receipt
unless @latest_receipt['original_transaction_id'] == @latest_receipt['transaction_id']
log "Updating receipt #{@ios_subscription.id}"
@ios_subscription.expires_date = @latest_receipt['expires_date']
@ios_subscription.expires_date_ms = @latest_receipt['expires_date_ms']
@ios_subscription.expires_date_pst = @latest_receipt['expires_date_pst']
@ios_subscription.web_order_line_item_id = @latest_receipt['web_order_line_item_id']
@ios_subscription.transaction_id = @latest_receipt['transaction_id']
@ios_subscription.save
end
end
end
end
def new_receipt
@ios_subscription = IosSubscription.new
@ios_subscription.original_purchase_date = @latest_receipt['original_purchase_date']
@ios_subscription.original_purchase_date_pst = @latest_receipt['original_purchase_date_pst']
@ios_subscription.original_purchase_date_ms = @latest_receipt['original_purchase_date_ms']
@ios_subscription.purchase_date = @latest_receipt['purchase_date']
@ios_subscription.purchase_date_pst = @latest_receipt['purchase_date_pst']
@ios_subscription.purchase_date_ms = @latest_receipt['purchase_date_ms']
@ios_subscription.expires_date = @latest_receipt['expires_date']
@ios_subscription.expires_date_ms = @latest_receipt['expires_date_ms']
@ios_subscription.expires_date_pst = @latest_receipt['expires_date_pst']
@ios_subscription.original_transaction_id = @latest_receipt['original_transaction_id']
@ios_subscription.transaction_id = @latest_receipt['transaction_id']
product_id = @latest_receipt['product_id']
@ios_subscription.product_id = product_id.split("_").first
@ios_subscription.quantity = @latest_receipt['quantity']
@ios_subscription.web_order_line_item_id = @latest_receipt['web_order_line_item_id']
@ios_subscription.udid = @UDID
@ios_subscription.receipt_data = @receipt_data
@ios_subscription.mode = @mode
# Associate ios subscription to the current user
unless @user.nil?
@user.ios_subscription = @ios_subscription
@user.save
end
@ios_subscription.save
end
Okay, this is very similar to the in-app purchase. We’re validating
the receipt with Apple, and if it’s valid save the receipt. If a
subscription already exists, we then need to update the expiry date
accordingly.checkouts_controller:def verify_subscription_ios
#...
if @resp.code == '200'
if json_resp['status'] == 0
save_receipt(json_resp)
elsif json_resp['status'] == 21007
@mode = "sandbox"
json_resp = validate_receipt("https://sandbox.itunes.apple.com/verifyReceipt")
save_receipt(json_resp)
else
respond_to do |format|
format.json { render json: {status: "invalid", mode: @mode, errorCode: "#{json_resp['status']}"} and return }
end
end
#...
end
If the error code is 21007, set the mode to ‘sandbox’ and validate it against the sandbox environment.
Comments
Post a Comment