Skip to content

Instantly share code, notes, and snippets.

@lukaspili
Created Sep 14, 2020
Embed
What would you like to do?
require 'google/apis/androidpublisher_v3'
class IapService
def self.validate_receipt(source:, sku:, transaction_id:, transaction_receipt:)
if source == :appstore
validate_appstore(
source: source,
sku: sku,
transaction_id: transaction_id,
transaction_receipt: transaction_receipt
)
elsif source == :playstore
validate_playstore(
source: source,
sku: sku,
transaction_receipt: transaction_receipt
)
else
false
end
end
private
def self.validate_appstore(source:, sku:, transaction_id:, transaction_receipt:)
# Validate first on production
result = validate_appstore_on(
url: "https://buy.itunes.apple.com/verifyReceipt",
source: source,
sku: sku,
transaction_id: transaction_id,
transaction_receipt: transaction_receipt
)
# If validation fails because of sandbox receipt, validate on sandbox
if result == :receipt_is_sandbox
result = validate_appstore_on(
url: "https://sandbox.itunes.apple.com/verifyReceipt",
source: source,
sku: sku,
transaction_id: transaction_id,
transaction_receipt: transaction_receipt
)
end
return result == :valid
end
def self.validate_appstore_on(url:, source:, sku:, transaction_id:, transaction_receipt:)
url = URI.parse(url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
response = nil
begin
response = http.post(
url.path,
{'receipt-data' => transaction_receipt }.to_json,
{'Content-Type' => 'application/x-www-form-urlencoded'}
)
rescue => error
AppLogger.error "IAP validate receipt failure", error
Raven.extra_context(
sku: sku,
receipt: transaction_receipt
)
Raven.capture_exception(error)
return :invalid
end
if response.nil?
return :invalid
end
AppLogger.info "IAP - validate receipt response code: #{response.code}"
AppLogger.info "IAP - validate receipt response body: #{response.body}"
if response.code != "200"
AppLogger.error "IAP: invalid response code => #{response.code}"
return :invalid
end
response = JSON.parse(response.body)
AppLogger.info "IAP - validate receipt response json: #{response}"
return validate_appstore_response(response: response, sku: sku, transaction_id: transaction_id)
end
# Exemple of json response: https://gist.github.com/sauloarruda/2559455/3e7db3f2e711cbf3be9ed9b1b3dd91b94738a792
def self.validate_appstore_response(response:, sku:, transaction_id:)
if response["status"] != 0
AppLogger.error "IAP: invalid status => #{response["status"]}"
return response["status"] == 21007 ? :receipt_is_sandbox : :invalid
end
response_product = response["receipt"]["in_app"].first
response_transaction_id = response_product["transaction_id"]
if response_transaction_id != transaction_id
AppLogger.error "IAP: transaction id does not match => #{response_transaction_id} != #{transaction_id}"
return :invalid
end
response_sku = response_product["product_id"]
if response_sku != sku
AppLogger.error "IAP: sku does not match => #{response_sku} != #{sku}"
return :invalid
end
return :valid
end
# Good doc: https://stackoverflow.com/questions/35127086/android-inapp-purchase-receipt-validation-google-play
def self.validate_playstore(source:, sku:, transaction_receipt:)
android_publisher = Google::Apis::AndroidpublisherV3::AndroidPublisherService.new
android_publisher.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open(Rails.root.join("config", "google-api-credentials.json")),
scope: "https://www.googleapis.com/auth/androidpublisher"
)
response = nil
begin
response = android_publisher.get_purchase_product("talknjoy.app", sku, transaction_receipt)
rescue => error
AppLogger.error "IAP validate receipt failure", error, show_stack: false
Raven.extra_context(
sku: sku,
receipt: transaction_receipt
)
Raven.capture_exception(error)
end
if response.nil?
return :invalid
end
AppLogger.info "IAP - validate receipt response: #{response}"
# Non null response means valid IAP
return :valid
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment