├── .rspec ├── Gemfile ├── .rubocop.yml ├── .gitignore ├── lib ├── itunes_receipt_validator │ ├── version.rb │ ├── error.rb │ ├── transactions_proxy.rb │ ├── remote.rb │ ├── receipt.rb │ └── transaction.rb └── itunes_receipt_validator.rb ├── Rakefile ├── .travis.yml ├── spec ├── spec_helper.rb ├── itunes_receipt_validator_spec.rb ├── shared │ └── shared_contexts.rb ├── remote │ ├── expired_transaction.json │ ├── subscription_transaction.json │ └── unified.json ├── itunes_receipt_validator │ ├── transaction_spec.rb │ ├── transactions_proxy_spec.rb │ └── receipt_spec.rb └── receipts │ ├── subscription_transaction.txt │ └── unified.txt ├── LICENSE ├── itunes_receipt_validator.gemspec ├── CODE_OF_CONDUCT.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Metrics/AbcSize: 2 | Enabled: false 3 | Metrics/MethodLength: 4 | Enabled: false 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | .DS_Store 3 | *.gem 4 | Gemfile.lock 5 | .ruby-version 6 | .ruby-gemset 7 | .bundle 8 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/version.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ItunesReceiptValidator 3 | module ItunesReceiptValidator 4 | ## 5 | # Gem version 6 | VERSION = '0.0.7'.freeze 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | RSpec::Core::RakeTask.new(:spec) 6 | task default: [:spec, :rubocop] 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.0.0 5 | - 2.1.6 6 | - 2.2.0 7 | 8 | install: 9 | - bundle install --path vendor/bundle 10 | 11 | script: 12 | - bundle exec rake 13 | 14 | addons: 15 | code_climate: 16 | repo_token: 461b4af1d91b670cc99fe13f99e3303014190179d7d93e6578e4a4eee9e502ca 17 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/error.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ItunesReceiptValidator 3 | module ItunesReceiptValidator 4 | ## 5 | # ItunesReceiptValidator::Error 6 | class Error < StandardError; end 7 | 8 | ## 9 | # ItunesReceiptValidator::LocalDecodingError 10 | class LocalDecodingError < Error; end 11 | 12 | ## 13 | # ItunesReceiptValidator::RemoteNetworkError 14 | class RemoteNetworkError < Error; end 15 | end 16 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator.rb: -------------------------------------------------------------------------------- 1 | require 'itunes_receipt_decoder' 2 | require 'itunes_receipt_validator/receipt' 3 | require 'itunes_receipt_validator/error' 4 | require 'itunes_receipt_validator/remote' 5 | require 'itunes_receipt_validator/transaction' 6 | require 'itunes_receipt_validator/transactions_proxy' 7 | 8 | ## 9 | # ItunesReceiptValidator 10 | module ItunesReceiptValidator 11 | def self.new(receipt, options = {}, &block) 12 | Receipt.new(receipt, options, &block) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/transactions_proxy.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ItunesReceiptValidator 3 | module ItunesReceiptValidator 4 | ## 5 | # ItunesReceiptValidator::TransactionsProxy 6 | class TransactionsProxy < Array 7 | def self.import(array, receipt) 8 | new array.map { |t| Transaction.new(t, receipt) }.sort_by(&:purchased_at) 9 | end 10 | 11 | def where(props) 12 | select do |t| 13 | !props.map { |key, val| t.send(key.to_sym) == val }.include?(false) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['CODECLIMATE_REPO_TOKEN'] 2 | require 'codeclimate-test-reporter' 3 | CodeClimate::TestReporter.start 4 | else 5 | require 'simplecov' 6 | SimpleCov.start 7 | end 8 | 9 | require 'webmock/rspec' 10 | require 'timecop' 11 | require 'securerandom' 12 | require 'itunes_receipt_validator' 13 | require 'shared/shared_contexts' 14 | 15 | WebMock.disable_net_connect!(allow: 'codeclimate.com') 16 | 17 | RSpec.configure do |config| 18 | config.order = :random 19 | Kernel.srand config.seed 20 | end 21 | -------------------------------------------------------------------------------- /spec/itunes_receipt_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ItunesReceiptValidator do 4 | shared_examples :a_receipt do 5 | describe '.new' do 6 | subject { described_class.new(receipt) } 7 | 8 | it 'returns an instance of Receipt' do 9 | expect(subject).to be_a(described_class::Receipt) 10 | end 11 | 12 | context 'when a block is given' do 13 | it 'assigns the shared_secret' do 14 | validator = described_class.new(receipt) do |receipt| 15 | receipt.shared_secret = 'bacon_and_eggs' 16 | end 17 | expect(validator.shared_secret).to eq('bacon_and_eggs') 18 | end 19 | end 20 | end 21 | end 22 | 23 | context 'with a unified stye receipt' do 24 | include_context :unified_receipt 25 | it_behaves_like :a_receipt 26 | end 27 | 28 | context 'with a subscription transaction style receipt' do 29 | include_context :subscription_transaction_receipt 30 | it_behaves_like :a_receipt 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 mbaasy.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /itunes_receipt_validator.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 3 | require 'itunes_receipt_validator/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'itunes_receipt_validator' 7 | spec.version = ItunesReceiptValidator::VERSION 8 | spec.summary = 'Validate iTunes OS X and iOS receipts' 9 | spec.description = <<-EOF 10 | Validate iTunes Transaction and Unified style receipts with local decoding 11 | and remote validation. 12 | EOF 13 | spec.license = 'MIT' 14 | spec.authors = ['mbaasy.com'] 15 | spec.email = 'hello@mbaasy.com' 16 | spec.homepage = 'https://github.com/mbaasy/itunes_receipt_validator' 17 | 18 | spec.required_ruby_version = '>= 2.0.0' 19 | 20 | spec.files = Dir['lib/**/*.rb'].reverse 21 | spec.require_paths = ['lib'] 22 | 23 | spec.add_dependency 'itunes_receipt_decoder', '0.3.1' 24 | 25 | spec.add_development_dependency 'rake', '~> 10.5' 26 | spec.add_development_dependency 'rspec', '~> 3.4' 27 | spec.add_development_dependency 'rubygems-tasks', '~> 0.2' 28 | spec.add_development_dependency 'simplecov', '~> 0.11' 29 | spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4' 30 | spec.add_development_dependency 'rubocop', '~> 0.37' 31 | spec.add_development_dependency 'webmock', '~> 1.24' 32 | spec.add_development_dependency 'timecop', '0.8' 33 | end 34 | -------------------------------------------------------------------------------- /spec/shared/shared_contexts.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_context :subscription_transaction_receipt do 4 | let(:receipt) { File.read(receipt_path).chomp } 5 | let(:remote_json) { File.read(remote_path) } 6 | let(:remote_hash) { JSON.parse(remote_json, symbolize_names: true) } 7 | let(:remote_transaction) { remote_hash[:receipt] } 8 | let(:expires_at) { Time.at remote_transaction[:expires_date].to_i / 1000 } 9 | let(:receipt_path) do 10 | File.expand_path('../../receipts/subscription_transaction.txt', __FILE__) 11 | end 12 | let(:remote_path) do 13 | File.expand_path('../../remote/subscription_transaction.json', __FILE__) 14 | end 15 | end 16 | 17 | shared_context :expired_subscription_transaction_receipt do 18 | let(:receipt) { File.read(receipt_path).chomp } 19 | let(:remote_json) { File.read(remote_path) } 20 | let(:remote_hash) { JSON.parse(remote_json, symbolize_names: true) } 21 | let(:remote_transaction) { remote_hash[:receipt] } 22 | let(:expires_at) { Time.at remote_transaction[:expires_date].to_i / 1000 } 23 | let(:receipt_path) do 24 | File.expand_path('../../receipts/subscription_transaction.txt', __FILE__) 25 | end 26 | let(:remote_path) do 27 | File.expand_path('../../remote/expired_transaction.json', __FILE__) 28 | end 29 | end 30 | 31 | shared_context :unified_receipt do 32 | let(:receipt) { File.read(receipt_path).chomp } 33 | let(:remote_json) { File.read(remote_path) } 34 | let(:remote_hash) { JSON.parse(remote_json, symbolize_names: true) } 35 | let(:remote_transactions) { remote_hash[:receipt][:in_app] } 36 | let(:receipt_path) do 37 | File.expand_path('../../receipts/unified.txt', __FILE__) 38 | end 39 | let(:remote_path) do 40 | File.expand_path('../../remote/unified.json', __FILE__) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/remote.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'uri' 3 | require 'net/https' 4 | 5 | ## 6 | # ItunesReceiptValidator 7 | module ItunesReceiptValidator 8 | ## 9 | # ItunesReceiptValidator::Receipt 10 | class Remote 11 | PRODUCTION_ENDPOINT = 'https://buy.itunes.apple.com/verifyReceipt'.freeze 12 | SANDBOX_ENDPOINT = 'https://sandbox.itunes.apple.com/verifyReceipt'.freeze 13 | 14 | attr_reader :receipt 15 | attr_accessor :sandbox, :shared_secret, :request_method 16 | 17 | def initialize(receipt) 18 | @receipt = receipt 19 | @request_method = lambda do |url, headers, body| 20 | default_request_method(url, headers, body) 21 | end 22 | yield self 23 | end 24 | 25 | def status 26 | json[:status].to_i 27 | end 28 | 29 | def valid? 30 | status.zero? 31 | end 32 | 33 | def expired? 34 | status == 21_006 35 | end 36 | 37 | def json 38 | @json ||= JSON.parse(response.body, symbolize_names: true) 39 | end 40 | 41 | private 42 | 43 | def request_url 44 | sandbox ? SANDBOX_ENDPOINT : PRODUCTION_ENDPOINT 45 | end 46 | 47 | def request_headers 48 | { 49 | 'Accept' => 'application/json', 50 | 'Content-Type' => 'application/json' 51 | } 52 | end 53 | 54 | def request_body 55 | { 56 | 'password' => shared_secret, 57 | 'receipt-data' => receipt 58 | }.to_json 59 | end 60 | 61 | def response 62 | @response ||= request_method.call( 63 | request_url, request_headers, request_body 64 | ) 65 | end 66 | 67 | def default_request_method(url, headers, body) 68 | uri = URI(url) 69 | http = Net::HTTP.new(uri.host, uri.port) 70 | http.use_ssl = true 71 | http.verify_mode = OpenSSL::SSL::VERIFY_PEER 72 | request = Net::HTTP::Post.new(uri.request_uri) 73 | headers.each { |key, val| request[key] = val } 74 | request.body = body 75 | http.request request 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/remote/expired_transaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "receipt": { 3 | "original_purchase_date_pst": "2015-11-17 08:34:41 America/Los_Angeles", 4 | "unique_identifier": "dd0e33ebf56bb1f3ad4c2dbaa7a0b33ac68b0e85", 5 | "original_transaction_id": "1000000180636226", 6 | "expires_date": "1448346881000", 7 | "transaction_id": "1000000181298734", 8 | "quantity": "1", 9 | "product_id": "yearly", 10 | "item_id": "1028950797", 11 | "bid": "com.mbaasy.ios.demo", 12 | "unique_vendor_identifier": "4E5E1A3B-F01A-4855-820F-F349F1242148", 13 | "web_order_line_item_id": "1000000030938713", 14 | "bvrs": "1", 15 | "expires_date_formatted": "2015-11-24 06:34:41 Etc/GMT", 16 | "purchase_date": "2015-11-18 04:34:41 Etc/GMT", 17 | "purchase_date_ms": "1447821281000", 18 | "expires_date_formatted_pst": "2015-11-23 22:34:41 America/Los_Angeles", 19 | "purchase_date_pst": "2015-11-17 20:34:41 America/Los_Angeles", 20 | "original_purchase_date": "2015-11-17 16:34:41 Etc/GMT", 21 | "original_purchase_date_ms": "1447778081000" 22 | }, 23 | "latest_expired_receipt_info": { 24 | "original_purchase_date_pst": "2015-11-17 08:34:41 America/Los_Angeles", 25 | "unique_identifier": "dd0e33ebf56bb1f3ad4c2dbaa7a0b33ac68b0e85", 26 | "original_transaction_id": "1000000180636226", 27 | "expires_date": "1448346881000", 28 | "transaction_id": "1000000180703127", 29 | "quantity": "1", 30 | "product_id": "yearly", 31 | "item_id": "1028950797", 32 | "bid": "com.mbaasy.ios.demo", 33 | "unique_vendor_identifier": "3F27583E-3E39-4865-A9F2-98256C105CDF", 34 | "web_order_line_item_id": "1000000030938713", 35 | "bvrs": "1", 36 | "expires_date_formatted": "2015-11-24 06:34:41 Etc/GMT", 37 | "purchase_date": "2015-11-18 04:34:41 Etc/GMT", 38 | "purchase_date_ms": "1447821281000", 39 | "expires_date_formatted_pst": "2015-11-23 22:34:41 America/Los_Angeles", 40 | "purchase_date_pst": "2015-11-17 20:34:41 America/Los_Angeles", 41 | "original_purchase_date": "2015-11-17 16:34:41 Etc/GMT", 42 | "original_purchase_date_ms": "1447778081000" 43 | }, 44 | "status": 21006 45 | } 46 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/receipt.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ItunesReceiptValidator 3 | module ItunesReceiptValidator 4 | ## 5 | # ItunesReceiptValidator::Receipt 6 | class Receipt 7 | attr_reader :receipt 8 | attr_accessor :shared_secret, :request_method 9 | 10 | def initialize(receipt, options = {}) 11 | @receipt = receipt 12 | @shared_secret = options.fetch(:shared_secret, nil) 13 | @request_method = options.fetch(:request_method, nil) 14 | local 15 | yield self if block_given? 16 | end 17 | 18 | def bundle_id 19 | @bundle_id = local.receipt.fetch(style == :unified ? :bundle_id : :bid) 20 | end 21 | 22 | def transactions 23 | @transactions = TransactionsProxy.import( 24 | local_transactions_source, self 25 | ) 26 | end 27 | 28 | def latest_transactions 29 | @latest_transactions = TransactionsProxy.import( 30 | remote_transactions_source, self 31 | ) 32 | end 33 | 34 | def latest_receipt 35 | @latest_receipt = remote.json.fetch :latest_receipt, nil 36 | end 37 | 38 | def local 39 | @local ||= ItunesReceiptDecoder.new receipt, expand_timestamps: true 40 | rescue ItunesReceiptDecoder::DecodingError => e 41 | raise LocalDecodingError, e.message 42 | end 43 | 44 | def environment 45 | @environment ||= local.environment 46 | end 47 | 48 | def production? 49 | environment == :production 50 | end 51 | 52 | def sandbox? 53 | !production? 54 | end 55 | 56 | def style 57 | local.style 58 | end 59 | 60 | def remote 61 | return @remote if @remote 62 | instance = Remote.new(receipt) do |remote| 63 | remote.shared_secret = shared_secret 64 | remote.sandbox = sandbox? 65 | remote.request_method = request_method if request_method 66 | end 67 | 68 | if instance.status == 21_007 69 | @environment = :sandbox 70 | remote 71 | elsif instance.status == 21_008 72 | @environment = :production 73 | remote 74 | else 75 | @remote = instance 76 | end 77 | end 78 | 79 | private 80 | 81 | def local_transactions_source 82 | style == :unified ? local.receipt.fetch(:in_app, []) : [local.receipt] 83 | end 84 | 85 | def remote_transactions_source 86 | if style == :unified 87 | remote.json.fetch(:latest_receipt_info, []) 88 | elsif remote.expired? 89 | [remote.json.fetch(:latest_expired_receipt_info, nil)].compact 90 | else 91 | [remote.json.fetch(:latest_receipt_info, nil)].compact 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/itunes_receipt_validator/transaction_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ItunesReceiptValidator::Transaction do 4 | subject { ItunesReceiptValidator.new(receipt).transactions.first } 5 | 6 | before do 7 | stub_request(:post, 'https://sandbox.itunes.apple.com/verifyReceipt') 8 | .to_return(body: remote_json) 9 | end 10 | 11 | shared_examples :a_transaction do 12 | it 'includes all expected properties' do 13 | expect(subject).to respond_to( 14 | :id, :original_id, :quantity, :first_purchased_at, 15 | :purchased_at, :expires_at, :cancelled_at 16 | ) 17 | end 18 | 19 | it 'parses all the timestamps' do 20 | %i(first_purchased_at purchased_at).each do |prop| 21 | expect(subject.send(prop)).to be_a(Time) 22 | end 23 | end 24 | end 25 | 26 | shared_examples :a_latest_transaction do 27 | describe '#latest' do 28 | it 'returns a Transaction instance' do 29 | expect(subject.latest).to be_a(described_class) 30 | end 31 | 32 | it 'has the same original_id' do 33 | expect(subject.latest.original_id).to eq(subject.original_id) 34 | end 35 | 36 | it 'has a different id' do 37 | expect(subject.latest.id).to_not eq(subject.id) 38 | end 39 | end 40 | end 41 | 42 | context 'with a unified style receipt' do 43 | include_context :unified_receipt 44 | it_behaves_like :a_transaction 45 | end 46 | 47 | context 'with a subscription transaction style receipt' do 48 | include_context :subscription_transaction_receipt 49 | it_behaves_like :a_transaction 50 | it_behaves_like :a_latest_transaction do 51 | describe '#expired?' do 52 | context 'when the expiry date is in the future' do 53 | before do 54 | Timecop.travel(expires_at - 3600) 55 | end 56 | 57 | it 'returns false' do 58 | expect(subject.latest.expired?).to eq(false) 59 | end 60 | end 61 | 62 | context 'when the expiry date is in the past' do 63 | before do 64 | Timecop.travel(expires_at + 3600) 65 | end 66 | 67 | it 'returns true' do 68 | expect(subject.latest.expired?).to eq(true) 69 | end 70 | end 71 | end 72 | end 73 | end 74 | 75 | context 'with a expired subscription transaction style receipt' do 76 | include_context :expired_subscription_transaction_receipt 77 | it_behaves_like :a_transaction 78 | it_behaves_like :a_latest_transaction do 79 | describe '#expired?' do 80 | it 'returns true' do 81 | expect(subject.latest.expired?).to eq(true) 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/itunes_receipt_validator/transaction.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | 3 | ## 4 | # ItunesReceiptValidator 5 | module ItunesReceiptValidator 6 | ## 7 | # ItunesReceiptValidator::Transaction 8 | class Transaction 9 | ATTR_MAP = { 10 | id: :transaction_id, 11 | original_id: :original_transaction_id, 12 | product_id: :product_id, 13 | quantity: :quantity, 14 | web_order_line_item_id: proc do |hash| 15 | hash[:web_order_line_item_id].to_s if 16 | hash.fetch(:web_order_line_item_id, 0).to_i > 0 17 | end, 18 | trial_period: ->(hash) { hash[:is_trial_period] == 'true' }, 19 | purchased_at: proc do |hash| 20 | parse_date(hash[:purchase_date_ms] || hash[:purchase_date]) 21 | end, 22 | first_purchased_at: proc do |hash| 23 | parse_date hash[:original_purchase_date_ms] || 24 | hash[:original_purchase_date] 25 | end, 26 | cancelled_at: proc do |hash| 27 | parse_date(hash[:cancelled_date_ms] || hash[:cancelled_date]) 28 | end, 29 | expires_at: proc do |hash| 30 | if hash[:bid] 31 | parse_date(hash[:expires_date] || hash[:expires_date_formatted]) 32 | else 33 | parse_date(hash[:expires_date_ms] || hash[:expires_date]) 34 | end 35 | end 36 | }.freeze 37 | 38 | attr_reader :id, :original_id, :product_id, :quantity, :first_purchased_at, 39 | :purchased_at, :expires_at, :cancelled_at, 40 | :web_order_line_item_id, :trial_period, :receipt 41 | 42 | def initialize(hash, receipt) 43 | normalize(hash) 44 | @receipt = receipt 45 | end 46 | 47 | def expired? 48 | !auto_renewing? || 49 | receipt.remote.expired? || 50 | latest.expires_at < Time.now.utc 51 | end 52 | 53 | def cancelled? 54 | !cancelled_at.nil? 55 | end 56 | 57 | def auto_renewing? 58 | !web_order_line_item_id.nil? 59 | end 60 | 61 | def trial_period? 62 | trial_period 63 | end 64 | 65 | def latest 66 | receipt.latest_transactions.where(original_id: original_id).last 67 | end 68 | 69 | private 70 | 71 | attr_writer :id, :original_id, :product_id, :quantity, 72 | :web_order_line_item_id, :trial_period, :purchased_at, 73 | :first_purchased_at, :cancelled_at, :expires_at 74 | 75 | def normalize(hash) 76 | ATTR_MAP.each do |key, val| 77 | if val.is_a?(Proc) 78 | send("#{key}=".to_s, instance_exec(hash, &val)) 79 | else 80 | send("#{key}=".to_s, hash[val]) 81 | end 82 | end 83 | end 84 | 85 | def parse_date(date) 86 | return nil if date.nil? 87 | if date.is_a?(Integer) || !(date =~ /^\d+$/).nil? 88 | Time.at(date.to_f / 1000).utc 89 | else 90 | Time.strptime(date, '%F %T Etc/%Z').utc 91 | end 92 | rescue StandardError => _e 93 | nil 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/receipts/subscription_transaction.txt: -------------------------------------------------------------------------------- 1 | ewoJInNpZ25hdHVyZSIgPSAiQW5USlN6UUFqZWhXWW1ucWxvZk9ZVnFyWEo1MVVOWnI5Ly8ySFhxM01COWkyYVBqVmlsdjM4aXhtWm9PLzlZZlBsUkhZRHVzWFQySXBZYkRzNHBGWk53L21RTDFUemtJSWV0WWVhNE95anVWNUtsdUVCNExLVm9sN25tSGZkMjdISTZQTTZqQkRaS0xtcGt0bU5WQ21mbmhlVCtqbE1qTHg3ZVpLakhTRmhsUkFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEUwTURZd056QXdNREl5TVZvWERURTJNRFV4T0RFNE16RXpNRm93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNbVRFdUxnamltTHdSSnh5MW9FZjBlc1VORFZFSWU2d0Rzbm5hbDE0aE5CdDF2MTk1WDZuOTNZTzdnaTNvclBTdXg5RDU1NFNrTXArU2F5Zzg0bFRjMzYyVXRtWUxwV25iMzRucXlHeDlLQlZUeTVPR1Y0bGpFMU93QytvVG5STStRTFJDbWVOeE1iUFpoUzQ3VCtlWnRERWhWQjl1c2szK0pNMkNvZ2Z3bzdBZ01CQUFHamNqQndNQjBHQTFVZERnUVdCQlNKYUVlTnVxOURmNlpmTjY4RmUrSTJ1MjJzc0RBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkRZZDZPS2RndElCR0xVeWF3N1hRd3VSV0VNNk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWVhSlYyVTUxcnhmY3FBQWU1QzIvZkVXOEtVbDRpTzRsTXV0YTdONlh6UDFwWkl6MU5ra0N0SUl3ZXlOajVVUllISytIalJLU1U5UkxndU5sMG5rZnhxT2JpTWNrd1J1ZEtTcTY5Tkluclp5Q0Q2NlI0Szc3bmI5bE1UQUJTU1lsc0t0OG9OdGxoZ1IvMWtqU1NSUWNIa3RzRGNTaVFHS01ka1NscDRBeVhmN3ZuSFBCZTR5Q3dZVjJQcFNOMDRrYm9pSjNwQmx4c0d3Vi9abEwyNk0ydWVZSEtZQ3VYaGRxRnd4VmdtNTJoM29lSk9PdC92WTRFY1FxN2VxSG02bTAzWjliN1BSellNMktHWEhEbU9Nazd2RHBlTVZsTERQU0dZejErVTNzRHhKemViU3BiYUptVDdpbXpVS2ZnZ0VZN3h4ZjRjemZIMHlqNXdOelNHVE92UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUxTFRFeExURTNJREE0T2pNME9qUXhJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0YlhNaUlEMGdJakUwTkRjNE1qRXlPREV3TURBaU93b0pJblZ1YVhGMVpTMXBaR1Z1ZEdsbWFXVnlJaUE5SUNKa1pEQmxNek5sWW1ZMU5tSmlNV1l6WVdRMFl6SmtZbUZoTjJFd1lqTXpZV00yT0dJd1pUZzFJanNLQ1NKdmNtbG5hVzVoYkMxMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaU1UQXdNREF3TURFNE1EWXpOakl5TmlJN0Nna2laWGh3YVhKbGN5MWtZWFJsSWlBOUlDSXhORFE0TXpRMk9EZ3hNREF3SWpzS0NTSjBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTRNVEk1T0Rjek5DSTdDZ2tpYjNKcFoybHVZV3d0Y0hWeVkyaGhjMlV0WkdGMFpTMXRjeUlnUFNBaU1UUTBOemMzT0RBNE1UQXdNQ0k3Q2draWQyVmlMVzl5WkdWeUxXeHBibVV0YVhSbGJTMXBaQ0lnUFNBaU1UQXdNREF3TURBek1Ea3pPRGN4TXlJN0Nna2lZblp5Y3lJZ1BTQWlNU0k3Q2draWRXNXBjWFZsTFhabGJtUnZjaTFwWkdWdWRHbG1hV1Z5SWlBOUlDSTBSVFZGTVVFelFpMUdNREZCTFRRNE5UVXRPREl3UmkxR016UTVSakV5TkRJeE5EZ2lPd29KSW1WNGNHbHlaWE10WkdGMFpTMW1iM0p0WVhSMFpXUXRjSE4wSWlBOUlDSXlNREUxTFRFeExUSXpJREl5T2pNME9qUXhJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkltbDBaVzB0YVdRaUlEMGdJakV3TWpnNU5UQTNPVGNpT3dvSkltVjRjR2x5WlhNdFpHRjBaUzFtYjNKdFlYUjBaV1FpSUQwZ0lqSXdNVFV0TVRFdE1qUWdNRFk2TXpRNk5ERWdSWFJqTDBkTlZDSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWllV1ZoY214NUlqc0tDU0p3ZFhKamFHRnpaUzFrWVhSbElpQTlJQ0l5TURFMUxURXhMVEU0SURBME9qTTBPalF4SUVWMFl5OUhUVlFpT3dvSkltOXlhV2RwYm1Gc0xYQjFjbU5vWVhObExXUmhkR1VpSUQwZ0lqSXdNVFV0TVRFdE1UY2dNVFk2TXpRNk5ERWdSWFJqTDBkTlZDSTdDZ2tpWW1sa0lpQTlJQ0pqYjIwdWJXSmhZWE41TG1sdmN5NWtaVzF2SWpzS0NTSndkWEpqYUdGelpTMWtZWFJsTFhCemRDSWdQU0FpTWpBeE5TMHhNUzB4TnlBeU1Eb3pORG8wTVNCQmJXVnlhV05oTDB4dmMxOUJibWRsYkdWeklqc0tDU0p4ZFdGdWRHbDBlU0lnUFNBaU1TSTdDbjA9IjsKCSJlbnZpcm9ubWVudCIgPSAiU2FuZGJveCI7CgkicG9kIiA9ICIxMDAiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ== 2 | -------------------------------------------------------------------------------- /spec/itunes_receipt_validator/transactions_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ItunesReceiptValidator::TransactionsProxy do 4 | subject { ItunesReceiptValidator.new(receipt).transactions } 5 | 6 | shared_examples :a_transactions_proxy do 7 | it 'contains an set of ItunesReceiptValidator::Transaction instances' do 8 | subject.each do |transaction| 9 | expect(transaction).to be_a(ItunesReceiptValidator::Transaction) 10 | end 11 | end 12 | end 13 | 14 | context 'with a unified style receipt' do 15 | include_context :unified_receipt 16 | it_behaves_like :a_transactions_proxy 17 | let(:managed_product_transactions) do 18 | remote_transactions.select { |t| !t.key?(:web_order_line_item_id) } 19 | end 20 | let(:subscription_product_transactions) do 21 | remote_transactions.select { |t| t.key?(:web_order_line_item_id) } 22 | end 23 | 24 | before do 25 | expect(managed_product_transactions.size).to be >= 1 26 | expect(subscription_product_transactions.size).to be >= 1 27 | end 28 | 29 | it 'contains the correct number of transactions' do 30 | expect(subject.size).to eq(remote_transactions.size) 31 | end 32 | 33 | it 'assigns attrs for all transactions' do 34 | remote_transactions.each do |t| 35 | transaction = subject.where(id: t[:transaction_id]).first 36 | expect(transaction.id).to eq(t[:transaction_id]) 37 | expect(transaction.product_id).to eq(t[:product_id]) 38 | expect(transaction.quantity.to_s).to eq(t[:quantity]) 39 | expect(transaction.purchased_at.strftime('%F %T Etc/GMT')) 40 | .to eq(t[:purchase_date]) 41 | expect(transaction.first_purchased_at.strftime('%F %T Etc/GMT')) 42 | .to eq(t[:original_purchase_date]) 43 | expect(transaction.web_order_line_item_id) 44 | .to eq(t[:web_order_line_item_id]) 45 | end 46 | end 47 | 48 | it 'assigns attrs for all managed product transactions' do 49 | managed_product_transactions.each do |t| 50 | transaction = subject.where(id: t[:transaction_id]).first 51 | expect(transaction.auto_renewing?).to eq(false) 52 | end 53 | end 54 | 55 | it 'assigns attrs for all subscription transactions' do 56 | subscription_product_transactions.each do |t| 57 | transaction = subject.where(id: t[:transaction_id]).first 58 | expect(transaction.auto_renewing?).to eq(true) 59 | expect(transaction.cancelled?).to eq(false) 60 | expect(transaction.trial_period?).to eq(false) 61 | expect(transaction.expires_at.strftime('%F %T Etc/GMT')) 62 | .to eq(t[:expires_date]) 63 | end 64 | end 65 | end 66 | 67 | context 'with a subscription transaction style receipt' do 68 | include_context :subscription_transaction_receipt 69 | it_behaves_like :a_transactions_proxy 70 | 71 | it 'contains only one transaction' do 72 | expect(subject.size).to eq(1) 73 | end 74 | 75 | it 'assigns attrs the transaction' do 76 | t = remote_transaction 77 | transaction = subject.where(id: t[:transaction_id]).first 78 | expect(transaction.id).to eq(t[:transaction_id]) 79 | expect(transaction.product_id).to eq(t[:product_id]) 80 | expect(transaction.quantity.to_s).to eq(t[:quantity]) 81 | expect(transaction.purchased_at.strftime('%F %T Etc/GMT')) 82 | .to eq(t[:purchase_date]) 83 | expect(transaction.first_purchased_at.strftime('%F %T Etc/GMT')) 84 | .to eq(t[:original_purchase_date]) 85 | expect(transaction.web_order_line_item_id) 86 | .to eq(t[:web_order_line_item_id]) 87 | expect(transaction.auto_renewing?).to eq(true) 88 | expect(transaction.cancelled?).to eq(false) 89 | expect(transaction.trial_period?).to eq(false) 90 | expect(transaction.expires_at.strftime('%F %T Etc/GMT')) 91 | .to eq(t[:expires_date_formatted]) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/remote/subscription_transaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "receipt": { 3 | "original_purchase_date_pst": "2015-11-17 08:34:41 America/Los_Angeles", 4 | "unique_identifier": "dd0e33ebf56bb1f3ad4c2dbaa7a0b33ac68b0e85", 5 | "original_transaction_id": "1000000180636226", 6 | "expires_date": "1448346881000", 7 | "transaction_id": "1000000181298734", 8 | "quantity": "1", 9 | "product_id": "yearly", 10 | "item_id": "1028950797", 11 | "bid": "com.mbaasy.ios.demo", 12 | "unique_vendor_identifier": "4E5E1A3B-F01A-4855-820F-F349F1242148", 13 | "web_order_line_item_id": "1000000030938713", 14 | "bvrs": "1", 15 | "expires_date_formatted": "2015-11-24 06:34:41 Etc/GMT", 16 | "purchase_date": "2015-11-18 04:34:41 Etc/GMT", 17 | "purchase_date_ms": "1447821281000", 18 | "expires_date_formatted_pst": "2015-11-23 22:34:41 America/Los_Angeles", 19 | "purchase_date_pst": "2015-11-17 20:34:41 America/Los_Angeles", 20 | "original_purchase_date": "2015-11-17 16:34:41 Etc/GMT", 21 | "original_purchase_date_ms": "1447778081000" 22 | }, 23 | "latest_receipt_info": { 24 | "original_purchase_date_pst": "2015-11-17 08:34:41 America/Los_Angeles", 25 | "unique_identifier": "dd0e33ebf56bb1f3ad4c2dbaa7a0b33ac68b0e85", 26 | "original_transaction_id": "1000000180636226", 27 | "expires_date": "1448346881000", 28 | "transaction_id": "1000000180703127", 29 | "quantity": "1", 30 | "product_id": "yearly", 31 | "item_id": "1028950797", 32 | "bid": "com.mbaasy.ios.demo", 33 | "unique_vendor_identifier": "3F27583E-3E39-4865-A9F2-98256C105CDF", 34 | "web_order_line_item_id": "1000000030938713", 35 | "bvrs": "1", 36 | "expires_date_formatted": "2015-11-24 06:34:41 Etc/GMT", 37 | "purchase_date": "2015-11-18 04:34:41 Etc/GMT", 38 | "purchase_date_ms": "1447821281000", 39 | "expires_date_formatted_pst": "2015-11-23 22:34:41 America/Los_Angeles", 40 | "purchase_date_pst": "2015-11-17 20:34:41 America/Los_Angeles", 41 | "original_purchase_date": "2015-11-17 16:34:41 Etc/GMT", 42 | "original_purchase_date_ms": "1447778081000" 43 | }, 44 | "status": 0, 45 | "latest_receipt": "ewoJInNpZ25hdHVyZSIgPSAiQWcxR2pyN3E4NmxxY0VJK05HMENSdUFibHJsa2twSlZ4ODU0cHZyRVMxeFZOSkpONVgxSUVzOHFJZWVZRksvZFVTYzVNRE9vZ0F0RFNjR2liSTFaTER3V0pQRnZnc3V2OTNBbXhMdDZQSlZiNXZaS1d5QlFUUUxzbkROU2UyS0RjSWd4Sis0Zks0bU5oY3hkcEZpa1hTWkZPUEE1RmNsMjQwN05yMGx4YjNoSEFBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEUwTURZd056QXdNREl5TVZvWERURTJNRFV4T0RFNE16RXpNRm93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNbVRFdUxnamltTHdSSnh5MW9FZjBlc1VORFZFSWU2d0Rzbm5hbDE0aE5CdDF2MTk1WDZuOTNZTzdnaTNvclBTdXg5RDU1NFNrTXArU2F5Zzg0bFRjMzYyVXRtWUxwV25iMzRucXlHeDlLQlZUeTVPR1Y0bGpFMU93QytvVG5STStRTFJDbWVOeE1iUFpoUzQ3VCtlWnRERWhWQjl1c2szK0pNMkNvZ2Z3bzdBZ01CQUFHamNqQndNQjBHQTFVZERnUVdCQlNKYUVlTnVxOURmNlpmTjY4RmUrSTJ1MjJzc0RBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkRZZDZPS2RndElCR0xVeWF3N1hRd3VSV0VNNk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWVhSlYyVTUxcnhmY3FBQWU1QzIvZkVXOEtVbDRpTzRsTXV0YTdONlh6UDFwWkl6MU5ra0N0SUl3ZXlOajVVUllISytIalJLU1U5UkxndU5sMG5rZnhxT2JpTWNrd1J1ZEtTcTY5Tkluclp5Q0Q2NlI0Szc3bmI5bE1UQUJTU1lsc0t0OG9OdGxoZ1IvMWtqU1NSUWNIa3RzRGNTaVFHS01ka1NscDRBeVhmN3ZuSFBCZTR5Q3dZVjJQcFNOMDRrYm9pSjNwQmx4c0d3Vi9abEwyNk0ydWVZSEtZQ3VYaGRxRnd4VmdtNTJoM29lSk9PdC92WTRFY1FxN2VxSG02bTAzWjliN1BSellNMktHWEhEbU9Nazd2RHBlTVZsTERQU0dZejErVTNzRHhKemViU3BiYUptVDdpbXpVS2ZnZ0VZN3h4ZjRjemZIMHlqNXdOelNHVE92UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUxTFRFeExURTNJREE0T2pNME9qUXhJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0YlhNaUlEMGdJakUwTkRjNE1qRXlPREV3TURBaU93b0pJblZ1YVhGMVpTMXBaR1Z1ZEdsbWFXVnlJaUE5SUNKa1pEQmxNek5sWW1ZMU5tSmlNV1l6WVdRMFl6SmtZbUZoTjJFd1lqTXpZV00yT0dJd1pUZzFJanNLQ1NKdmNtbG5hVzVoYkMxMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaU1UQXdNREF3TURFNE1EWXpOakl5TmlJN0Nna2laWGh3YVhKbGN5MWtZWFJsSWlBOUlDSXhORFE0TXpRMk9EZ3hNREF3SWpzS0NTSjBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTRNRGN3TXpFeU55STdDZ2tpYjNKcFoybHVZV3d0Y0hWeVkyaGhjMlV0WkdGMFpTMXRjeUlnUFNBaU1UUTBOemMzT0RBNE1UQXdNQ0k3Q2draWQyVmlMVzl5WkdWeUxXeHBibVV0YVhSbGJTMXBaQ0lnUFNBaU1UQXdNREF3TURBek1Ea3pPRGN4TXlJN0Nna2lZblp5Y3lJZ1BTQWlNU0k3Q2draWRXNXBjWFZsTFhabGJtUnZjaTFwWkdWdWRHbG1hV1Z5SWlBOUlDSXpSakkzTlRnelJTMHpSVE01TFRRNE5qVXRRVGxHTWkwNU9ESTFOa014TURWRFJFWWlPd29KSW1WNGNHbHlaWE10WkdGMFpTMW1iM0p0WVhSMFpXUXRjSE4wSWlBOUlDSXlNREUxTFRFeExUSXpJREl5T2pNME9qUXhJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkltbDBaVzB0YVdRaUlEMGdJakV3TWpnNU5UQTNPVGNpT3dvSkltVjRjR2x5WlhNdFpHRjBaUzFtYjNKdFlYUjBaV1FpSUQwZ0lqSXdNVFV0TVRFdE1qUWdNRFk2TXpRNk5ERWdSWFJqTDBkTlZDSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWllV1ZoY214NUlqc0tDU0p3ZFhKamFHRnpaUzFrWVhSbElpQTlJQ0l5TURFMUxURXhMVEU0SURBME9qTTBPalF4SUVWMFl5OUhUVlFpT3dvSkltOXlhV2RwYm1Gc0xYQjFjbU5vWVhObExXUmhkR1VpSUQwZ0lqSXdNVFV0TVRFdE1UY2dNVFk2TXpRNk5ERWdSWFJqTDBkTlZDSTdDZ2tpWW1sa0lpQTlJQ0pqYjIwdWJXSmhZWE41TG1sdmN5NWtaVzF2SWpzS0NTSndkWEpqYUdGelpTMWtZWFJsTFhCemRDSWdQU0FpTWpBeE5TMHhNUzB4TnlBeU1Eb3pORG8wTVNCQmJXVnlhV05oTDB4dmMxOUJibWRsYkdWeklqc0tDU0p4ZFdGdWRHbDBlU0lnUFNBaU1TSTdDbjA9IjsKCSJlbnZpcm9ubWVudCIgPSAiU2FuZGJveCI7CgkicG9kIiA9ICIxMDAiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==" 46 | } 47 | -------------------------------------------------------------------------------- /spec/itunes_receipt_validator/receipt_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ItunesReceiptValidator::Receipt do 4 | let(:options) { { shared_secret: SecureRandom.hex(20) } } 5 | let(:instance) { described_class.new(receipt, options) } 6 | 7 | before do 8 | stub_request(:post, 'https://sandbox.itunes.apple.com/verifyReceipt') 9 | .to_return(body: remote_json) 10 | end 11 | 12 | shared_examples :an_itunes_sandbox_result do 13 | it 'fetches the data from sandbox.itunes.apple.com' do 14 | subject 15 | expect( 16 | a_request(:post, 'https://sandbox.itunes.apple.com/verifyReceipt') 17 | .with( 18 | headers: { 19 | 'Accept' => 'application/json', 20 | 'Content-Type' => 'application/json' 21 | }, 22 | body: { 23 | 'password' => options.fetch(:shared_secret), 24 | 'receipt-data' => receipt 25 | }.to_json 26 | ) 27 | ).to have_been_made.once 28 | end 29 | end 30 | 31 | shared_examples :an_itunes_production_result do 32 | it 'fetches the data from buy.itunes.apple.com' do 33 | subject 34 | expect( 35 | a_request(:post, 'https://buy.itunes.apple.com/verifyReceipt') 36 | .with( 37 | headers: { 38 | 'Accept' => 'application/json', 39 | 'Content-Type' => 'application/json' 40 | }, 41 | body: { 42 | 'password' => options.fetch(:shared_secret), 43 | 'receipt-data' => receipt 44 | }.to_json 45 | ) 46 | ).to have_been_made.once 47 | end 48 | end 49 | 50 | shared_examples :a_receipt do 51 | describe '#bundle_id' do 52 | subject { instance.bundle_id } 53 | 54 | it 'returns the bundle id' do 55 | expect(subject).to eq('com.mbaasy.ios.demo') 56 | end 57 | end 58 | 59 | describe '#sandbox?' do 60 | subject { instance.sandbox? } 61 | 62 | context 'when the receipt is from sandbox' do 63 | before do 64 | allow_any_instance_of(ItunesReceiptDecoder::Decode::Base) 65 | .to receive(:environment).and_return(:sandbox) 66 | end 67 | 68 | it 'returns true' do 69 | expect(subject).to eq(true) 70 | end 71 | end 72 | 73 | context 'when the receipt is from production' do 74 | before do 75 | allow_any_instance_of(ItunesReceiptDecoder::Decode::Base) 76 | .to receive(:environment).and_return(:production) 77 | end 78 | 79 | it 'returns false' do 80 | expect(subject).to eq(false) 81 | end 82 | end 83 | end 84 | 85 | describe '#production?' do 86 | subject { instance.production? } 87 | 88 | context 'when the receipt is from sandbox' do 89 | before do 90 | allow_any_instance_of(ItunesReceiptDecoder::Decode::Base) 91 | .to receive(:environment).and_return(:sandbox) 92 | end 93 | 94 | it 'returns false' do 95 | expect(subject).to eq(false) 96 | end 97 | end 98 | 99 | context 'when the receipt is from production' do 100 | before do 101 | allow_any_instance_of(ItunesReceiptDecoder::Decode::Base) 102 | .to receive(:environment).and_return(:production) 103 | end 104 | 105 | it 'returns true' do 106 | expect(subject).to eq(true) 107 | end 108 | end 109 | end 110 | 111 | describe '#transactions' do 112 | subject { instance.transactions } 113 | 114 | it 'returns an instance of ItunesReceiptValidator::TransactionsProxy' do 115 | expect(subject).to be_a(ItunesReceiptValidator::TransactionsProxy) 116 | end 117 | end 118 | 119 | describe '#latest_transactions' do 120 | subject { instance.latest_transactions } 121 | 122 | it_behaves_like :an_itunes_sandbox_result 123 | 124 | it 'returns an instance of ItunesReceiptValidator::TransactionsProxy' do 125 | expect(subject).to be_a(ItunesReceiptValidator::TransactionsProxy) 126 | end 127 | end 128 | 129 | describe '#latest_receipt' do 130 | subject { instance.latest_receipt } 131 | 132 | it_behaves_like :an_itunes_sandbox_result 133 | 134 | it 'returns the latest_receipt' do 135 | expect(subject).to eq(JSON.parse(remote_json, symbolize_names: true) 136 | .fetch(:latest_receipt)) 137 | end 138 | end 139 | 140 | describe '#remote' do 141 | subject { instance.remote } 142 | 143 | context 'when decoder.environment was production but status is 21_007' do 144 | before do 145 | allow_any_instance_of(ItunesReceiptDecoder::Decode::Base) 146 | .to receive(:environment).and_return(:production) 147 | stub_request(:post, 'https://buy.itunes.apple.com/verifyReceipt') 148 | .to_return(body: { status: 21_007 }.to_json) 149 | end 150 | 151 | it 'changes the environment' do 152 | expect { subject }.to change { 153 | instance.environment 154 | }.from(:production).to(:sandbox) 155 | end 156 | 157 | it_behaves_like :an_itunes_sandbox_result 158 | end 159 | 160 | context 'when decoder.environment was sandbox but status is 21_008' do 161 | before do 162 | stub_request(:post, 'https://buy.itunes.apple.com/verifyReceipt') 163 | .to_return(body: remote_json) 164 | stub_request(:post, 'https://sandbox.itunes.apple.com/verifyReceipt') 165 | .to_return(body: { status: 21_008 }.to_json) 166 | end 167 | 168 | it 'changes the environment' do 169 | expect { subject }.to change { 170 | instance.environment 171 | }.from(:sandbox).to(:production) 172 | end 173 | 174 | it_behaves_like :an_itunes_production_result 175 | end 176 | end 177 | end 178 | 179 | context 'with a unified style receipt' do 180 | include_context :unified_receipt 181 | it_behaves_like :a_receipt 182 | end 183 | 184 | context 'with a subscription transaction style receipt' do 185 | include_context :subscription_transaction_receipt 186 | it_behaves_like :a_receipt 187 | end 188 | 189 | context 'with a expired subscription transaction style receipt' do 190 | include_context :expired_subscription_transaction_receipt 191 | 192 | describe '#latest_transactions' do 193 | subject { instance.latest_transactions } 194 | 195 | it_behaves_like :an_itunes_sandbox_result 196 | 197 | it 'returns an instance of ItunesReceiptValidator::TransactionsProxy' do 198 | expect(subject).to be_a(ItunesReceiptValidator::TransactionsProxy) 199 | end 200 | end 201 | 202 | describe '#remote' do 203 | subject { instance.remote } 204 | 205 | describe '#expired?' do 206 | subject { super().expired? } 207 | 208 | it 'returns true' do 209 | expect(subject).to eq(true) 210 | end 211 | end 212 | 213 | describe '#valid?' do 214 | subject { super().valid? } 215 | 216 | it 'returns false' do 217 | expect(subject).to eq(false) 218 | end 219 | end 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /spec/receipts/unified.txt: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iTunes Receipt Validator 2 | 3 | [![Code Climate](https://codeclimate.com/github/mbaasy/itunes_receipt_validator/badges/gpa.svg)](https://codeclimate.com/github/mbaasy/itunes_receipt_validator) 4 | [![Test Coverage](https://codeclimate.com/github/mbaasy/itunes_receipt_validator/badges/coverage.svg)](https://codeclimate.com/github/mbaasy/itunes_receipt_validator/coverage) 5 | [![Build Status](https://travis-ci.org/mbaasy/itunes_receipt_validator.svg?branch=master)](https://travis-ci.org/mbaasy/itunes_receipt_validator) 6 | [![Gem Version](https://badge.fury.io/rb/itunes_receipt_validator.svg)](https://badge.fury.io/rb/itunes_receipt_validator) 7 | 8 | ## Decode locally 9 | 10 | The difference between this gem and any of the alternatives is that it decodes the base64 encoded receipt with our [ItunesReceiptDecoder](https://github.com/mbaasy/itunes_receipt_decoder) library to extract the data from the receipt **without** making a HTTP request to Apple's servers. 11 | 12 | ## No redundent HTTP requests 13 | 14 | Because this library decodes the receipt first, it determins the origin of the receipt before making any HTTP requests. This means you don't need to make an additional request to the sandbox or production URLs. 15 | 16 | Secondly, if the receipt can't be decoded, it can't be validated. This will prevent unnecessary requests when you receive fraudulent receipts. 17 | 18 | ## Handle any style of receipt 19 | 20 | Apple offers two kinds of receipts: 21 | 22 | 1. The deprecated [[SKPaymentTransaction transactionReceipt]](https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKPaymentTransaction_Class/#//apple_ref/occ/instp/SKPaymentTransaction/transactionReceipt) and; 23 | 1. The Grand Unified Receipt [[NSBundle appStoreReceiptURL]](https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/#//apple_ref/occ/instp/NSBundle/appStoreReceiptURL) 24 | 25 | Validating both kinds of receipts requires separate logic because the schemas and data are entirely different. 26 | 27 | ## Normalize all the things 28 | 29 | No matter if the receipt is a `[SKPaymentTransaction transactionReceipt]` or `[NSBundle appStoreReceiptURL]`, the responses are normalized with extra helpful methods for all your iOS and OSX receipt validation needs. 30 | 31 | ## Need a complete solution? 32 | 33 | We have a cloud service available at **[mbaasy.com](https://mbaasy.com)**, it takes care of in-app purchase management, receipt validation and reporting so you don't have to. It even offers integration with your own API through webhooks to notify you of new, expired, renewed and cancelled purchases for iOS, OSX and Google Play receipts. It will save you time and money but most of all it allows you to focus on your core project instead of wasting time on receipt validation. 34 | 35 | We offer this libary because we believe in the Open Source movement, [mbaasy.com](https://mbaasy.com) is built upon a foundation of Open Source projects, so this libary is our way of giving back to the community. 36 | 37 | ## Install 38 | 39 | Install from the command line: 40 | 41 | ```sh 42 | $ gem install itunes_receipt_validator 43 | ``` 44 | 45 | Or include it in your Gemfile: 46 | 47 | ```ruby 48 | gem 'itunes_receipt_validator' 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### Initialization 54 | 55 | ```ruby 56 | validator = ItunesReceiptValidator.new( 57 | base64_encoded_receipt, 58 | shared_secret: 'your_shared_secret' 59 | ) # => ItunesReceiptValidator::Receipt 60 | ``` 61 | 62 | Or intialize with a block: 63 | 64 | ```ruby 65 | shared_secrets = { 66 | 'com.example.app1' => 'shared_secret_for_app1' 67 | 'com.example.app2' => 'shared_secret_for_app2' 68 | } 69 | validator = ItunesReceiptValidator.new(base64_encoded_receipt) do |receipt| 70 | receipt.shared_secret = shared_secrets.fetch(receipt.bundle_id) 71 | end 72 | ``` 73 | 74 | ### BYO HTTP requests 75 | 76 | If you require more flexibility with upstream HTTP requests to Apple's Validation API you can initialize with your own request method. 77 | 78 | ```ruby 79 | validator = ItunesReceiptValidator.new(base64_encoded_receipt) do |receipt| 80 | receipt.shared_secret = 'your_shared_secret' 81 | receipt.request_method = lambda do |url, headers, body| 82 | MyFancyRequest.new(url, headers: headers, body: body) 83 | end 84 | end 85 | ``` 86 | 87 | Your custom method exposes a HTTP status code and a response body as `status` and `body` respectively. 88 | 89 | ### ItunesReceiptValidator::Receipt methods 90 | 91 | ```ruby 92 | validator.sandbox? 93 | ``` 94 | Returns true or false (opposite of production?). 95 | 96 | ```ruby 97 | validator.production? 98 | ``` 99 | Returns true or false (opposite of sandbox?). 100 | 101 | ```ruby 102 | validator.bundle_id 103 | ``` 104 | Returns the bundle_id of the app (e.g. com.mbaasy.ios). 105 | 106 | ```ruby 107 | validator.transactions 108 | ``` 109 | Returns a sub-class of `Array`, with transactions sourced locally from the receipt. See [ItunesReceiptValidator::TransactionsProxy methods](#itunesreceiptvalidatortransactionsproxy-methods). 110 | 111 | ```ruby 112 | validator.latest_transactions 113 | ``` 114 | Returns a sub-class of `Array`, with transactions sourced from Apple's validation API. See [ItunesReceiptValidator::TransactionsProxy methods](#itunesreceiptvalidatortransactionsproxy-methods). 115 | 116 | ```ruby 117 | validator.latest_receipt 118 | ``` 119 | Returns a base64 encoded string for the latest receipt sourced from Apple's validation API. 120 | 121 | ```ruby 122 | validator.local 123 | ``` 124 | Returns the `ItunesReceiptDecoder::Decode::Transaction` or `ItunesReceiptDecoder::Decode::Unified` instances. See the [ItunesReceiptDecoder](https://github.com/mbaasy/itunes_receipt_decoder) gem. 125 | 126 | ```ruby 127 | validator.remote 128 | ``` 129 | Returns the `ItunesReceiptValidator::Remote` instance. 130 | 131 | ### ItunesReceiptValidator::TransactionsProxy methods 132 | 133 | Inerhits from [Array](http://apidock.com/ruby/Array) and includes [Enumerable](http://apidock.com/ruby/Enumerable). 134 | 135 | ```ruby 136 | transactions.where(id: 1234) 137 | ``` 138 | Like ActiveRecord's `where` it accepts a hash of arguments and returns a new instance of `ItunesReceiptValidator::TransactionsProxy`. 139 | 140 | ### ItunesReceiptValidator::Transaction methods 141 | 142 | ```ruby 143 | transaction.expired? 144 | ``` 145 | Returns true or false. This method will make a request to Apple's validation API to check if the receipt itself has expired, or if the latest transaction retrieved is expired. 146 | 147 | ```ruby 148 | transaction.cancelled? 149 | ``` 150 | Returns true or false. 151 | 152 | ```ruby 153 | transaction.auto_renewing? 154 | ``` 155 | Returns true if `web_order_line_item_id` is present. 156 | 157 | ```ruby 158 | transaction.trial_period? 159 | ``` 160 | Returns true or false. 161 | 162 | ```ruby 163 | transaction.latest 164 | ``` 165 | Returns a `ItunesReceiptValidator::Transaction` by making a request to Apple's validation API and matches it with the `original_id`. 166 | 167 | ### ItunesReceiptValidator::Transaction properties 168 | 169 | ``` ruby 170 | transaction.id 171 | ``` 172 | The transaction identifier of the item that was purchased. 173 | 174 | See [Transaction Identifier](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW13). 175 | 176 | ```ruby 177 | transaction.original_id 178 | ``` 179 | For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. 180 | 181 | See [Original Transaction Identifier](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW14). 182 | 183 | ```ruby 184 | transaction.product_id 185 | ``` 186 | The product identifier of the item that was purchased. 187 | 188 | See [Product Identifier](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW11). 189 | 190 | ```ruby 191 | transaction.quantity 192 | ``` 193 | The number of items purchased. 194 | 195 | See [Quantity](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW10). 196 | 197 | ```ruby 198 | transaction.first_purchased_at 199 | ``` 200 | For a transaction that restores a previous transaction, the date of the original transaction. Cast as a `Time` instance. 201 | 202 | See [Original Purchase Date](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW4). 203 | 204 | ```ruby 205 | transaction.purchased_at 206 | ``` 207 | The date and time that the item was purchased. Cast as a `Time` instance. 208 | 209 | See [Purchase Date](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW15). 210 | 211 | ```ruby 212 | transaction.expires_at 213 | ``` 214 | The expiration date for the subscription. Cast as a `Time` instance. 215 | 216 | See [Subscription Expiration Date](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW28). 217 | 218 | ```ruby 219 | transaction.cancelled_at 220 | ``` 221 | For a transaction that was canceled by Apple customer support, the time and date of the cancellation. Cast as a `Time` instance. 222 | 223 | See [Cancellation Date](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW19). 224 | 225 | ```ruby 226 | transaction.web_order_line_item_id 227 | ``` 228 | The primary key for identifying subscription purchases. 229 | 230 | See [Web Order Line Item ID](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW17). 231 | 232 | ```ruby 233 | transaction.trial_period 234 | ``` 235 | 236 | Undocumented with Apple. 237 | 238 | ## Testing 239 | 240 | 1. `bundle install` 241 | 1. `rake` 242 | 243 | --- 244 | 245 | Copyright 2015 [mbaasy.com](https://mbaasy.com/). This project is subject to the [MIT License](/LICENSE). 246 | -------------------------------------------------------------------------------- /spec/remote/unified.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 0, 3 | "environment": "Sandbox", 4 | "receipt": { 5 | "receipt_type": "ProductionSandbox", 6 | "adam_id": 0, 7 | "app_item_id": 0, 8 | "bundle_id": "com.mbaasy.ios.demo", 9 | "application_version": "1", 10 | "download_id": 0, 11 | "version_external_identifier": 0, 12 | "receipt_creation_date": "2015-08-13 07:50:46 Etc/GMT", 13 | "receipt_creation_date_ms": "1439452246000", 14 | "receipt_creation_date_pst": "2015-08-13 00:50:46 America/Los_Angeles", 15 | "request_date": "2015-11-21 18:17:47 Etc/GMT", 16 | "request_date_ms": "1448129867409", 17 | "request_date_pst": "2015-11-21 10:17:47 America/Los_Angeles", 18 | "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT", 19 | "original_purchase_date_ms": "1375340400000", 20 | "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles", 21 | "original_application_version": "1.0", 22 | "in_app": [ 23 | { 24 | "quantity": "1", 25 | "product_id": "consumable", 26 | "transaction_id": "1000000166865231", 27 | "original_transaction_id": "1000000166865231", 28 | "purchase_date": "2015-08-07 20:37:55 Etc/GMT", 29 | "purchase_date_ms": "1438979875000", 30 | "purchase_date_pst": "2015-08-07 13:37:55 America/Los_Angeles", 31 | "original_purchase_date": "2015-08-07 20:37:55 Etc/GMT", 32 | "original_purchase_date_ms": "1438979875000", 33 | "original_purchase_date_pst": "2015-08-07 13:37:55 America/Los_Angeles", 34 | "is_trial_period": "false" 35 | }, 36 | { 37 | "quantity": "1", 38 | "product_id": "monthly", 39 | "transaction_id": "1000000166965150", 40 | "original_transaction_id": "1000000166965150", 41 | "purchase_date": "2015-08-10 06:49:32 Etc/GMT", 42 | "purchase_date_ms": "1439189372000", 43 | "purchase_date_pst": "2015-08-09 23:49:32 America/Los_Angeles", 44 | "original_purchase_date": "2015-08-10 06:49:33 Etc/GMT", 45 | "original_purchase_date_ms": "1439189373000", 46 | "original_purchase_date_pst": "2015-08-09 23:49:33 America/Los_Angeles", 47 | "expires_date": "2015-08-10 06:54:32 Etc/GMT", 48 | "expires_date_ms": "1439189672000", 49 | "expires_date_pst": "2015-08-09 23:54:32 America/Los_Angeles", 50 | "web_order_line_item_id": "1000000030274153", 51 | "is_trial_period": "false" 52 | }, 53 | { 54 | "quantity": "1", 55 | "product_id": "monthly", 56 | "transaction_id": "1000000166965327", 57 | "original_transaction_id": "1000000166965150", 58 | "purchase_date": "2015-08-10 06:54:32 Etc/GMT", 59 | "purchase_date_ms": "1439189672000", 60 | "purchase_date_pst": "2015-08-09 23:54:32 America/Los_Angeles", 61 | "original_purchase_date": "2015-08-10 06:53:18 Etc/GMT", 62 | "original_purchase_date_ms": "1439189598000", 63 | "original_purchase_date_pst": "2015-08-09 23:53:18 America/Los_Angeles", 64 | "expires_date": "2015-08-10 06:59:32 Etc/GMT", 65 | "expires_date_ms": "1439189972000", 66 | "expires_date_pst": "2015-08-09 23:59:32 America/Los_Angeles", 67 | "web_order_line_item_id": "1000000030274154", 68 | "is_trial_period": "false" 69 | }, 70 | { 71 | "quantity": "1", 72 | "product_id": "monthly", 73 | "transaction_id": "1000000166965895", 74 | "original_transaction_id": "1000000166965150", 75 | "purchase_date": "2015-08-10 06:59:32 Etc/GMT", 76 | "purchase_date_ms": "1439189972000", 77 | "purchase_date_pst": "2015-08-09 23:59:32 America/Los_Angeles", 78 | "original_purchase_date": "2015-08-10 06:57:34 Etc/GMT", 79 | "original_purchase_date_ms": "1439189854000", 80 | "original_purchase_date_pst": "2015-08-09 23:57:34 America/Los_Angeles", 81 | "expires_date": "2015-08-10 07:04:32 Etc/GMT", 82 | "expires_date_ms": "1439190272000", 83 | "expires_date_pst": "2015-08-10 00:04:32 America/Los_Angeles", 84 | "web_order_line_item_id": "1000000030274165", 85 | "is_trial_period": "false" 86 | }, 87 | { 88 | "quantity": "1", 89 | "product_id": "monthly", 90 | "transaction_id": "1000000166967152", 91 | "original_transaction_id": "1000000166965150", 92 | "purchase_date": "2015-08-10 07:04:32 Etc/GMT", 93 | "purchase_date_ms": "1439190272000", 94 | "purchase_date_pst": "2015-08-10 00:04:32 America/Los_Angeles", 95 | "original_purchase_date": "2015-08-10 07:02:33 Etc/GMT", 96 | "original_purchase_date_ms": "1439190153000", 97 | "original_purchase_date_pst": "2015-08-10 00:02:33 America/Los_Angeles", 98 | "expires_date": "2015-08-10 07:09:32 Etc/GMT", 99 | "expires_date_ms": "1439190572000", 100 | "expires_date_pst": "2015-08-10 00:09:32 America/Los_Angeles", 101 | "web_order_line_item_id": "1000000030274192", 102 | "is_trial_period": "false" 103 | }, 104 | { 105 | "quantity": "1", 106 | "product_id": "monthly", 107 | "transaction_id": "1000000166967484", 108 | "original_transaction_id": "1000000166965150", 109 | "purchase_date": "2015-08-10 07:09:32 Etc/GMT", 110 | "purchase_date_ms": "1439190572000", 111 | "purchase_date_pst": "2015-08-10 00:09:32 America/Los_Angeles", 112 | "original_purchase_date": "2015-08-10 07:08:30 Etc/GMT", 113 | "original_purchase_date_ms": "1439190510000", 114 | "original_purchase_date_pst": "2015-08-10 00:08:30 America/Los_Angeles", 115 | "expires_date": "2015-08-10 07:14:32 Etc/GMT", 116 | "expires_date_ms": "1439190872000", 117 | "expires_date_pst": "2015-08-10 00:14:32 America/Los_Angeles", 118 | "web_order_line_item_id": "1000000030274219", 119 | "is_trial_period": "false" 120 | }, 121 | { 122 | "quantity": "1", 123 | "product_id": "monthly", 124 | "transaction_id": "1000000166967782", 125 | "original_transaction_id": "1000000166965150", 126 | "purchase_date": "2015-08-10 07:14:32 Etc/GMT", 127 | "purchase_date_ms": "1439190872000", 128 | "purchase_date_pst": "2015-08-10 00:14:32 America/Los_Angeles", 129 | "original_purchase_date": "2015-08-10 07:12:34 Etc/GMT", 130 | "original_purchase_date_ms": "1439190754000", 131 | "original_purchase_date_pst": "2015-08-10 00:12:34 America/Los_Angeles", 132 | "expires_date": "2015-08-10 07:19:32 Etc/GMT", 133 | "expires_date_ms": "1439191172000", 134 | "expires_date_pst": "2015-08-10 00:19:32 America/Los_Angeles", 135 | "web_order_line_item_id": "1000000030274249", 136 | "is_trial_period": "false" 137 | } 138 | ] 139 | }, 140 | "latest_receipt_info": [ 141 | { 142 | "quantity": "1", 143 | "product_id": "consumable", 144 | "transaction_id": "1000000166865231", 145 | "original_transaction_id": "1000000166865231", 146 | "purchase_date": "2015-08-07 20:37:55 Etc/GMT", 147 | "purchase_date_ms": "1438979875000", 148 | "purchase_date_pst": "2015-08-07 13:37:55 America/Los_Angeles", 149 | "original_purchase_date": "2015-08-07 20:37:55 Etc/GMT", 150 | "original_purchase_date_ms": "1438979875000", 151 | "original_purchase_date_pst": "2015-08-07 13:37:55 America/Los_Angeles", 152 | "is_trial_period": "false" 153 | }, 154 | { 155 | "quantity": "1", 156 | "product_id": "monthly", 157 | "transaction_id": "1000000166965150", 158 | "original_transaction_id": "1000000166965150", 159 | "purchase_date": "2015-08-10 06:49:32 Etc/GMT", 160 | "purchase_date_ms": "1439189372000", 161 | "purchase_date_pst": "2015-08-09 23:49:32 America/Los_Angeles", 162 | "original_purchase_date": "2015-08-10 06:49:33 Etc/GMT", 163 | "original_purchase_date_ms": "1439189373000", 164 | "original_purchase_date_pst": "2015-08-09 23:49:33 America/Los_Angeles", 165 | "expires_date": "2015-08-10 06:54:32 Etc/GMT", 166 | "expires_date_ms": "1439189672000", 167 | "expires_date_pst": "2015-08-09 23:54:32 America/Los_Angeles", 168 | "web_order_line_item_id": "1000000030274153", 169 | "is_trial_period": "false" 170 | }, 171 | { 172 | "quantity": "1", 173 | "product_id": "monthly", 174 | "transaction_id": "1000000166965327", 175 | "original_transaction_id": "1000000166965150", 176 | "purchase_date": "2015-08-10 06:54:32 Etc/GMT", 177 | "purchase_date_ms": "1439189672000", 178 | "purchase_date_pst": "2015-08-09 23:54:32 America/Los_Angeles", 179 | "original_purchase_date": "2015-08-10 06:53:18 Etc/GMT", 180 | "original_purchase_date_ms": "1439189598000", 181 | "original_purchase_date_pst": "2015-08-09 23:53:18 America/Los_Angeles", 182 | "expires_date": "2015-08-10 06:59:32 Etc/GMT", 183 | "expires_date_ms": "1439189972000", 184 | "expires_date_pst": "2015-08-09 23:59:32 America/Los_Angeles", 185 | "web_order_line_item_id": "1000000030274154", 186 | "is_trial_period": "false" 187 | }, 188 | { 189 | "quantity": "1", 190 | "product_id": "monthly", 191 | "transaction_id": "1000000166965895", 192 | "original_transaction_id": "1000000166965150", 193 | "purchase_date": "2015-08-10 06:59:32 Etc/GMT", 194 | "purchase_date_ms": "1439189972000", 195 | "purchase_date_pst": "2015-08-09 23:59:32 America/Los_Angeles", 196 | "original_purchase_date": "2015-08-10 06:57:34 Etc/GMT", 197 | "original_purchase_date_ms": "1439189854000", 198 | "original_purchase_date_pst": "2015-08-09 23:57:34 America/Los_Angeles", 199 | "expires_date": "2015-08-10 07:04:32 Etc/GMT", 200 | "expires_date_ms": "1439190272000", 201 | "expires_date_pst": "2015-08-10 00:04:32 America/Los_Angeles", 202 | "web_order_line_item_id": "1000000030274165", 203 | "is_trial_period": "false" 204 | }, 205 | { 206 | "quantity": "1", 207 | "product_id": "monthly", 208 | "transaction_id": "1000000166967152", 209 | "original_transaction_id": "1000000166965150", 210 | "purchase_date": "2015-08-10 07:04:32 Etc/GMT", 211 | "purchase_date_ms": "1439190272000", 212 | "purchase_date_pst": "2015-08-10 00:04:32 America/Los_Angeles", 213 | "original_purchase_date": "2015-08-10 07:02:33 Etc/GMT", 214 | "original_purchase_date_ms": "1439190153000", 215 | "original_purchase_date_pst": "2015-08-10 00:02:33 America/Los_Angeles", 216 | "expires_date": "2015-08-10 07:09:32 Etc/GMT", 217 | "expires_date_ms": "1439190572000", 218 | "expires_date_pst": "2015-08-10 00:09:32 America/Los_Angeles", 219 | "web_order_line_item_id": "1000000030274192", 220 | "is_trial_period": "false" 221 | }, 222 | { 223 | "quantity": "1", 224 | "product_id": "monthly", 225 | "transaction_id": "1000000166967484", 226 | "original_transaction_id": "1000000166965150", 227 | "purchase_date": "2015-08-10 07:09:32 Etc/GMT", 228 | "purchase_date_ms": "1439190572000", 229 | "purchase_date_pst": "2015-08-10 00:09:32 America/Los_Angeles", 230 | "original_purchase_date": "2015-08-10 07:08:30 Etc/GMT", 231 | "original_purchase_date_ms": "1439190510000", 232 | "original_purchase_date_pst": "2015-08-10 00:08:30 America/Los_Angeles", 233 | "expires_date": "2015-08-10 07:14:32 Etc/GMT", 234 | "expires_date_ms": "1439190872000", 235 | "expires_date_pst": "2015-08-10 00:14:32 America/Los_Angeles", 236 | "web_order_line_item_id": "1000000030274219", 237 | "is_trial_period": "false" 238 | }, 239 | { 240 | "quantity": "1", 241 | "product_id": "monthly", 242 | "transaction_id": "1000000166967782", 243 | "original_transaction_id": "1000000166965150", 244 | "purchase_date": "2015-08-10 07:14:32 Etc/GMT", 245 | "purchase_date_ms": "1439190872000", 246 | "purchase_date_pst": "2015-08-10 00:14:32 America/Los_Angeles", 247 | "original_purchase_date": "2015-08-10 07:12:34 Etc/GMT", 248 | "original_purchase_date_ms": "1439190754000", 249 | "original_purchase_date_pst": "2015-08-10 00:12:34 America/Los_Angeles", 250 | "expires_date": "2015-08-10 07:19:32 Etc/GMT", 251 | "expires_date_ms": "1439191172000", 252 | "expires_date_pst": "2015-08-10 00:19:32 America/Los_Angeles", 253 | "web_order_line_item_id": "1000000030274249", 254 | "is_trial_period": "false" 255 | }, 256 | { 257 | "quantity": "1", 258 | "product_id": "yearly", 259 | "transaction_id": "1000000167535429", 260 | "original_transaction_id": "1000000166965150", 261 | "purchase_date": "2015-08-13 12:48:57 Etc/GMT", 262 | "purchase_date_ms": "1439470137000", 263 | "purchase_date_pst": "2015-08-13 05:48:57 America/Los_Angeles", 264 | "original_purchase_date": "2015-08-13 12:48:57 Etc/GMT", 265 | "original_purchase_date_ms": "1439470137000", 266 | "original_purchase_date_pst": "2015-08-13 05:48:57 America/Los_Angeles", 267 | "expires_date": "2015-08-13 13:48:57 Etc/GMT", 268 | "expires_date_ms": "1439473737000", 269 | "expires_date_pst": "2015-08-13 06:48:57 America/Los_Angeles", 270 | "web_order_line_item_id": "1000000030274264", 271 | "is_trial_period": "false" 272 | }, 273 | { 274 | "quantity": "1", 275 | "product_id": "yearly", 276 | "transaction_id": "1000000167542505", 277 | "original_transaction_id": "1000000166965150", 278 | "purchase_date": "2015-08-13 13:48:57 Etc/GMT", 279 | "purchase_date_ms": "1439473737000", 280 | "purchase_date_pst": "2015-08-13 06:48:57 America/Los_Angeles", 281 | "original_purchase_date": "2015-08-13 13:37:03 Etc/GMT", 282 | "original_purchase_date_ms": "1439473023000", 283 | "original_purchase_date_pst": "2015-08-13 06:37:03 America/Los_Angeles", 284 | "expires_date": "2015-08-13 14:48:57 Etc/GMT", 285 | "expires_date_ms": "1439477337000", 286 | "expires_date_pst": "2015-08-13 07:48:57 America/Los_Angeles", 287 | "web_order_line_item_id": "1000000030295293", 288 | "is_trial_period": "false" 289 | }, 290 | { 291 | "quantity": "1", 292 | "product_id": "yearly", 293 | "transaction_id": "1000000167551572", 294 | "original_transaction_id": "1000000166965150", 295 | "purchase_date": "2015-08-13 14:48:57 Etc/GMT", 296 | "purchase_date_ms": "1439477337000", 297 | "purchase_date_pst": "2015-08-13 07:48:57 America/Los_Angeles", 298 | "original_purchase_date": "2015-08-13 14:37:11 Etc/GMT", 299 | "original_purchase_date_ms": "1439476631000", 300 | "original_purchase_date_pst": "2015-08-13 07:37:11 America/Los_Angeles", 301 | "expires_date": "2015-08-13 15:48:57 Etc/GMT", 302 | "expires_date_ms": "1439480937000", 303 | "expires_date_pst": "2015-08-13 08:48:57 America/Los_Angeles", 304 | "web_order_line_item_id": "1000000030295566", 305 | "is_trial_period": "false" 306 | }, 307 | { 308 | "quantity": "1", 309 | "product_id": "yearly", 310 | "transaction_id": "1000000167557688", 311 | "original_transaction_id": "1000000166965150", 312 | "purchase_date": "2015-08-13 15:48:57 Etc/GMT", 313 | "purchase_date_ms": "1439480937000", 314 | "purchase_date_pst": "2015-08-13 08:48:57 America/Los_Angeles", 315 | "original_purchase_date": "2015-08-13 15:37:05 Etc/GMT", 316 | "original_purchase_date_ms": "1439480225000", 317 | "original_purchase_date_pst": "2015-08-13 08:37:05 America/Los_Angeles", 318 | "expires_date": "2015-08-13 16:48:57 Etc/GMT", 319 | "expires_date_ms": "1439484537000", 320 | "expires_date_pst": "2015-08-13 09:48:57 America/Los_Angeles", 321 | "web_order_line_item_id": "1000000030295891", 322 | "is_trial_period": "false" 323 | }, 324 | { 325 | "quantity": "1", 326 | "product_id": "yearly", 327 | "transaction_id": "1000000167565519", 328 | "original_transaction_id": "1000000166965150", 329 | "purchase_date": "2015-08-13 16:48:57 Etc/GMT", 330 | "purchase_date_ms": "1439484537000", 331 | "purchase_date_pst": "2015-08-13 09:48:57 America/Los_Angeles", 332 | "original_purchase_date": "2015-08-13 16:37:33 Etc/GMT", 333 | "original_purchase_date_ms": "1439483853000", 334 | "original_purchase_date_pst": "2015-08-13 09:37:33 America/Los_Angeles", 335 | "expires_date": "2015-08-13 17:48:57 Etc/GMT", 336 | "expires_date_ms": "1439488137000", 337 | "expires_date_pst": "2015-08-13 10:48:57 America/Los_Angeles", 338 | "web_order_line_item_id": "1000000030296272", 339 | "is_trial_period": "false" 340 | }, 341 | { 342 | "quantity": "1", 343 | "product_id": "yearly", 344 | "transaction_id": "1000000167573515", 345 | "original_transaction_id": "1000000166965150", 346 | "purchase_date": "2015-08-13 17:48:57 Etc/GMT", 347 | "purchase_date_ms": "1439488137000", 348 | "purchase_date_pst": "2015-08-13 10:48:57 America/Los_Angeles", 349 | "original_purchase_date": "2015-08-13 17:37:38 Etc/GMT", 350 | "original_purchase_date_ms": "1439487458000", 351 | "original_purchase_date_pst": "2015-08-13 10:37:38 America/Los_Angeles", 352 | "expires_date": "2015-08-13 18:48:57 Etc/GMT", 353 | "expires_date_ms": "1439491737000", 354 | "expires_date_pst": "2015-08-13 11:48:57 America/Los_Angeles", 355 | "web_order_line_item_id": "1000000030296515", 356 | "is_trial_period": "false" 357 | }, 358 | { 359 | "quantity": "1", 360 | "product_id": "consumable", 361 | "transaction_id": "1000000167942121", 362 | "original_transaction_id": "1000000167942121", 363 | "purchase_date": "2015-08-17 19:47:05 Etc/GMT", 364 | "purchase_date_ms": "1439840825000", 365 | "purchase_date_pst": "2015-08-17 12:47:05 America/Los_Angeles", 366 | "original_purchase_date": "2015-08-17 19:47:05 Etc/GMT", 367 | "original_purchase_date_ms": "1439840825000", 368 | "original_purchase_date_pst": "2015-08-17 12:47:05 America/Los_Angeles", 369 | "is_trial_period": "false" 370 | }, 371 | { 372 | "quantity": "1", 373 | "product_id": "weekly", 374 | "transaction_id": "1000000168081698", 375 | "original_transaction_id": "1000000166965150", 376 | "purchase_date": "2015-08-18 14:32:17 Etc/GMT", 377 | "purchase_date_ms": "1439908337000", 378 | "purchase_date_pst": "2015-08-18 07:32:17 America/Los_Angeles", 379 | "original_purchase_date": "2015-08-18 14:32:17 Etc/GMT", 380 | "original_purchase_date_ms": "1439908337000", 381 | "original_purchase_date_pst": "2015-08-18 07:32:17 America/Los_Angeles", 382 | "expires_date": "2015-08-18 14:35:17 Etc/GMT", 383 | "expires_date_ms": "1439908517000", 384 | "expires_date_pst": "2015-08-18 07:35:17 America/Los_Angeles", 385 | "web_order_line_item_id": "1000000030296790", 386 | "is_trial_period": "false" 387 | }, 388 | { 389 | "quantity": "1", 390 | "product_id": "weekly", 391 | "transaction_id": "1000000168082364", 392 | "original_transaction_id": "1000000166965150", 393 | "purchase_date": "2015-08-18 14:35:17 Etc/GMT", 394 | "purchase_date_ms": "1439908517000", 395 | "purchase_date_pst": "2015-08-18 07:35:17 America/Los_Angeles", 396 | "original_purchase_date": "2015-08-18 14:34:37 Etc/GMT", 397 | "original_purchase_date_ms": "1439908477000", 398 | "original_purchase_date_pst": "2015-08-18 07:34:37 America/Los_Angeles", 399 | "expires_date": "2015-08-18 14:38:17 Etc/GMT", 400 | "expires_date_ms": "1439908697000", 401 | "expires_date_pst": "2015-08-18 07:38:17 America/Los_Angeles", 402 | "web_order_line_item_id": "1000000030317283", 403 | "is_trial_period": "false" 404 | }, 405 | { 406 | "quantity": "1", 407 | "product_id": "weekly", 408 | "transaction_id": "1000000168082788", 409 | "original_transaction_id": "1000000166965150", 410 | "purchase_date": "2015-08-18 14:38:17 Etc/GMT", 411 | "purchase_date_ms": "1439908697000", 412 | "purchase_date_pst": "2015-08-18 07:38:17 America/Los_Angeles", 413 | "original_purchase_date": "2015-08-18 14:37:37 Etc/GMT", 414 | "original_purchase_date_ms": "1439908657000", 415 | "original_purchase_date_pst": "2015-08-18 07:37:37 America/Los_Angeles", 416 | "expires_date": "2015-08-18 14:41:17 Etc/GMT", 417 | "expires_date_ms": "1439908877000", 418 | "expires_date_pst": "2015-08-18 07:41:17 America/Los_Angeles", 419 | "web_order_line_item_id": "1000000030317292", 420 | "is_trial_period": "false" 421 | }, 422 | { 423 | "quantity": "1", 424 | "product_id": "weekly", 425 | "transaction_id": "1000000168083023", 426 | "original_transaction_id": "1000000166965150", 427 | "purchase_date": "2015-08-18 14:41:17 Etc/GMT", 428 | "purchase_date_ms": "1439908877000", 429 | "purchase_date_pst": "2015-08-18 07:41:17 America/Los_Angeles", 430 | "original_purchase_date": "2015-08-18 14:40:35 Etc/GMT", 431 | "original_purchase_date_ms": "1439908835000", 432 | "original_purchase_date_pst": "2015-08-18 07:40:35 America/Los_Angeles", 433 | "expires_date": "2015-08-18 14:44:17 Etc/GMT", 434 | "expires_date_ms": "1439909057000", 435 | "expires_date_pst": "2015-08-18 07:44:17 America/Los_Angeles", 436 | "web_order_line_item_id": "1000000030317306", 437 | "is_trial_period": "false" 438 | }, 439 | { 440 | "quantity": "1", 441 | "product_id": "weekly", 442 | "transaction_id": "1000000168083462", 443 | "original_transaction_id": "1000000166965150", 444 | "purchase_date": "2015-08-18 14:44:17 Etc/GMT", 445 | "purchase_date_ms": "1439909057000", 446 | "purchase_date_pst": "2015-08-18 07:44:17 America/Los_Angeles", 447 | "original_purchase_date": "2015-08-18 14:43:20 Etc/GMT", 448 | "original_purchase_date_ms": "1439909000000", 449 | "original_purchase_date_pst": "2015-08-18 07:43:20 America/Los_Angeles", 450 | "expires_date": "2015-08-18 14:47:17 Etc/GMT", 451 | "expires_date_ms": "1439909237000", 452 | "expires_date_pst": "2015-08-18 07:47:17 America/Los_Angeles", 453 | "web_order_line_item_id": "1000000030317319", 454 | "is_trial_period": "false" 455 | }, 456 | { 457 | "quantity": "1", 458 | "product_id": "weekly", 459 | "transaction_id": "1000000168083902", 460 | "original_transaction_id": "1000000166965150", 461 | "purchase_date": "2015-08-18 14:47:17 Etc/GMT", 462 | "purchase_date_ms": "1439909237000", 463 | "purchase_date_pst": "2015-08-18 07:47:17 America/Los_Angeles", 464 | "original_purchase_date": "2015-08-18 14:46:37 Etc/GMT", 465 | "original_purchase_date_ms": "1439909197000", 466 | "original_purchase_date_pst": "2015-08-18 07:46:37 America/Los_Angeles", 467 | "expires_date": "2015-08-18 14:50:17 Etc/GMT", 468 | "expires_date_ms": "1439909417000", 469 | "expires_date_pst": "2015-08-18 07:50:17 America/Los_Angeles", 470 | "web_order_line_item_id": "1000000030317335", 471 | "is_trial_period": "false" 472 | }, 473 | { 474 | "quantity": "1", 475 | "product_id": "nonconsumable", 476 | "transaction_id": "1000000168437870", 477 | "original_transaction_id": "1000000168437870", 478 | "purchase_date": "2015-08-20 16:09:33 Etc/GMT", 479 | "purchase_date_ms": "1440086973000", 480 | "purchase_date_pst": "2015-08-20 09:09:33 America/Los_Angeles", 481 | "original_purchase_date": "2015-08-20 16:09:33 Etc/GMT", 482 | "original_purchase_date_ms": "1440086973000", 483 | "original_purchase_date_pst": "2015-08-20 09:09:33 America/Los_Angeles", 484 | "is_trial_period": "false" 485 | }, 486 | { 487 | "quantity": "1", 488 | "product_id": "yearly", 489 | "transaction_id": "1000000168438074", 490 | "original_transaction_id": "1000000166965150", 491 | "purchase_date": "2015-08-20 16:12:42 Etc/GMT", 492 | "purchase_date_ms": "1440087162000", 493 | "purchase_date_pst": "2015-08-20 09:12:42 America/Los_Angeles", 494 | "original_purchase_date": "2015-08-20 16:12:43 Etc/GMT", 495 | "original_purchase_date_ms": "1440087163000", 496 | "original_purchase_date_pst": "2015-08-20 09:12:43 America/Los_Angeles", 497 | "expires_date": "2015-08-20 17:12:42 Etc/GMT", 498 | "expires_date_ms": "1440090762000", 499 | "expires_date_pst": "2015-08-20 10:12:42 America/Los_Angeles", 500 | "web_order_line_item_id": "1000000030317360", 501 | "is_trial_period": "false" 502 | }, 503 | { 504 | "quantity": "1", 505 | "product_id": "yearly", 506 | "transaction_id": "1000000168441473", 507 | "original_transaction_id": "1000000166965150", 508 | "purchase_date": "2015-08-20 17:12:42 Etc/GMT", 509 | "purchase_date_ms": "1440090762000", 510 | "purchase_date_pst": "2015-08-20 10:12:42 America/Los_Angeles", 511 | "original_purchase_date": "2015-08-20 17:01:14 Etc/GMT", 512 | "original_purchase_date_ms": "1440090074000", 513 | "original_purchase_date_pst": "2015-08-20 10:01:14 America/Los_Angeles", 514 | "expires_date": "2015-08-20 18:12:42 Etc/GMT", 515 | "expires_date_ms": "1440094362000", 516 | "expires_date_pst": "2015-08-20 11:12:42 America/Los_Angeles", 517 | "web_order_line_item_id": "1000000030331172", 518 | "is_trial_period": "false" 519 | }, 520 | { 521 | "quantity": "1", 522 | "product_id": "free_subscription", 523 | "transaction_id": "1000000168444630", 524 | "original_transaction_id": "1000000168444630", 525 | "purchase_date": "2015-08-20 17:40:33 Etc/GMT", 526 | "purchase_date_ms": "1440092433000", 527 | "purchase_date_pst": "2015-08-20 10:40:33 America/Los_Angeles", 528 | "original_purchase_date": "2015-08-20 17:40:33 Etc/GMT", 529 | "original_purchase_date_ms": "1440092433000", 530 | "original_purchase_date_pst": "2015-08-20 10:40:33 America/Los_Angeles", 531 | "is_trial_period": "false" 532 | }, 533 | { 534 | "quantity": "1", 535 | "product_id": "yearly", 536 | "transaction_id": "1000000168445624", 537 | "original_transaction_id": "1000000166965150", 538 | "purchase_date": "2015-08-20 18:12:42 Etc/GMT", 539 | "purchase_date_ms": "1440094362000", 540 | "purchase_date_pst": "2015-08-20 11:12:42 America/Los_Angeles", 541 | "original_purchase_date": "2015-08-20 18:01:19 Etc/GMT", 542 | "original_purchase_date_ms": "1440093679000", 543 | "original_purchase_date_pst": "2015-08-20 11:01:19 America/Los_Angeles", 544 | "expires_date": "2015-08-20 19:12:42 Etc/GMT", 545 | "expires_date_ms": "1440097962000", 546 | "expires_date_pst": "2015-08-20 12:12:42 America/Los_Angeles", 547 | "web_order_line_item_id": "1000000030331375", 548 | "is_trial_period": "false" 549 | }, 550 | { 551 | "quantity": "1", 552 | "product_id": "yearly", 553 | "transaction_id": "1000000168453379", 554 | "original_transaction_id": "1000000166965150", 555 | "purchase_date": "2015-08-20 19:12:42 Etc/GMT", 556 | "purchase_date_ms": "1440097962000", 557 | "purchase_date_pst": "2015-08-20 12:12:42 America/Los_Angeles", 558 | "original_purchase_date": "2015-08-20 19:00:44 Etc/GMT", 559 | "original_purchase_date_ms": "1440097244000", 560 | "original_purchase_date_pst": "2015-08-20 12:00:44 America/Los_Angeles", 561 | "expires_date": "2015-08-20 20:12:42 Etc/GMT", 562 | "expires_date_ms": "1440101562000", 563 | "expires_date_pst": "2015-08-20 13:12:42 America/Los_Angeles", 564 | "web_order_line_item_id": "1000000030331661", 565 | "is_trial_period": "false" 566 | }, 567 | { 568 | "quantity": "1", 569 | "product_id": "yearly", 570 | "transaction_id": "1000000168457082", 571 | "original_transaction_id": "1000000166965150", 572 | "purchase_date": "2015-08-20 20:12:42 Etc/GMT", 573 | "purchase_date_ms": "1440101562000", 574 | "purchase_date_pst": "2015-08-20 13:12:42 America/Los_Angeles", 575 | "original_purchase_date": "2015-08-20 20:00:43 Etc/GMT", 576 | "original_purchase_date_ms": "1440100843000", 577 | "original_purchase_date_pst": "2015-08-20 13:00:43 America/Los_Angeles", 578 | "expires_date": "2015-08-20 21:12:42 Etc/GMT", 579 | "expires_date_ms": "1440105162000", 580 | "expires_date_pst": "2015-08-20 14:12:42 America/Los_Angeles", 581 | "web_order_line_item_id": "1000000030331989", 582 | "is_trial_period": "false" 583 | }, 584 | { 585 | "quantity": "1", 586 | "product_id": "yearly", 587 | "transaction_id": "1000000168462599", 588 | "original_transaction_id": "1000000166965150", 589 | "purchase_date": "2015-08-20 21:12:42 Etc/GMT", 590 | "purchase_date_ms": "1440105162000", 591 | "purchase_date_pst": "2015-08-20 14:12:42 America/Los_Angeles", 592 | "original_purchase_date": "2015-08-20 21:00:56 Etc/GMT", 593 | "original_purchase_date_ms": "1440104456000", 594 | "original_purchase_date_pst": "2015-08-20 14:00:56 America/Los_Angeles", 595 | "expires_date": "2015-08-20 22:12:42 Etc/GMT", 596 | "expires_date_ms": "1440108762000", 597 | "expires_date_pst": "2015-08-20 15:12:42 America/Los_Angeles", 598 | "web_order_line_item_id": "1000000030332251", 599 | "is_trial_period": "false" 600 | }, 601 | { 602 | "quantity": "1", 603 | "product_id": "weekly", 604 | "transaction_id": "1000000168565711", 605 | "original_transaction_id": "1000000166965150", 606 | "purchase_date": "2015-08-21 11:02:24 Etc/GMT", 607 | "purchase_date_ms": "1440154944000", 608 | "purchase_date_pst": "2015-08-21 04:02:24 America/Los_Angeles", 609 | "original_purchase_date": "2015-08-21 11:02:25 Etc/GMT", 610 | "original_purchase_date_ms": "1440154945000", 611 | "original_purchase_date_pst": "2015-08-21 04:02:25 America/Los_Angeles", 612 | "expires_date": "2015-08-21 11:05:24 Etc/GMT", 613 | "expires_date_ms": "1440155124000", 614 | "expires_date_pst": "2015-08-21 04:05:24 America/Los_Angeles", 615 | "web_order_line_item_id": "1000000030332499", 616 | "is_trial_period": "false" 617 | }, 618 | { 619 | "quantity": "1", 620 | "product_id": "weekly", 621 | "transaction_id": "1000000168565863", 622 | "original_transaction_id": "1000000166965150", 623 | "purchase_date": "2015-08-21 11:05:24 Etc/GMT", 624 | "purchase_date_ms": "1440155124000", 625 | "purchase_date_pst": "2015-08-21 04:05:24 America/Los_Angeles", 626 | "original_purchase_date": "2015-08-21 11:04:30 Etc/GMT", 627 | "original_purchase_date_ms": "1440155070000", 628 | "original_purchase_date_pst": "2015-08-21 04:04:30 America/Los_Angeles", 629 | "expires_date": "2015-08-21 11:08:24 Etc/GMT", 630 | "expires_date_ms": "1440155304000", 631 | "expires_date_pst": "2015-08-21 04:08:24 America/Los_Angeles", 632 | "web_order_line_item_id": "1000000030335697", 633 | "is_trial_period": "false" 634 | }, 635 | { 636 | "quantity": "1", 637 | "product_id": "free_subscription", 638 | "transaction_id": "1000000168565885", 639 | "original_transaction_id": "1000000168565885", 640 | "purchase_date": "2015-08-21 11:04:55 Etc/GMT", 641 | "purchase_date_ms": "1440155095000", 642 | "purchase_date_pst": "2015-08-21 04:04:55 America/Los_Angeles", 643 | "original_purchase_date": "2015-08-21 11:04:55 Etc/GMT", 644 | "original_purchase_date_ms": "1440155095000", 645 | "original_purchase_date_pst": "2015-08-21 04:04:55 America/Los_Angeles", 646 | "is_trial_period": "false" 647 | }, 648 | { 649 | "quantity": "1", 650 | "product_id": "weekly", 651 | "transaction_id": "1000000168566132", 652 | "original_transaction_id": "1000000166965150", 653 | "purchase_date": "2015-08-21 11:08:24 Etc/GMT", 654 | "purchase_date_ms": "1440155304000", 655 | "purchase_date_pst": "2015-08-21 04:08:24 America/Los_Angeles", 656 | "original_purchase_date": "2015-08-21 11:07:30 Etc/GMT", 657 | "original_purchase_date_ms": "1440155250000", 658 | "original_purchase_date_pst": "2015-08-21 04:07:30 America/Los_Angeles", 659 | "expires_date": "2015-08-21 11:11:24 Etc/GMT", 660 | "expires_date_ms": "1440155484000", 661 | "expires_date_pst": "2015-08-21 04:11:24 America/Los_Angeles", 662 | "web_order_line_item_id": "1000000030335708", 663 | "is_trial_period": "false" 664 | }, 665 | { 666 | "quantity": "1", 667 | "product_id": "weekly", 668 | "transaction_id": "1000000168566636", 669 | "original_transaction_id": "1000000166965150", 670 | "purchase_date": "2015-08-21 11:11:24 Etc/GMT", 671 | "purchase_date_ms": "1440155484000", 672 | "purchase_date_pst": "2015-08-21 04:11:24 America/Los_Angeles", 673 | "original_purchase_date": "2015-08-21 11:10:30 Etc/GMT", 674 | "original_purchase_date_ms": "1440155430000", 675 | "original_purchase_date_pst": "2015-08-21 04:10:30 America/Los_Angeles", 676 | "expires_date": "2015-08-21 11:14:24 Etc/GMT", 677 | "expires_date_ms": "1440155664000", 678 | "expires_date_pst": "2015-08-21 04:14:24 America/Los_Angeles", 679 | "web_order_line_item_id": "1000000030335727", 680 | "is_trial_period": "false" 681 | }, 682 | { 683 | "quantity": "1", 684 | "product_id": "weekly", 685 | "transaction_id": "1000000168567065", 686 | "original_transaction_id": "1000000166965150", 687 | "purchase_date": "2015-08-21 11:14:24 Etc/GMT", 688 | "purchase_date_ms": "1440155664000", 689 | "purchase_date_pst": "2015-08-21 04:14:24 America/Los_Angeles", 690 | "original_purchase_date": "2015-08-21 11:13:30 Etc/GMT", 691 | "original_purchase_date_ms": "1440155610000", 692 | "original_purchase_date_pst": "2015-08-21 04:13:30 America/Los_Angeles", 693 | "expires_date": "2015-08-21 11:17:24 Etc/GMT", 694 | "expires_date_ms": "1440155844000", 695 | "expires_date_pst": "2015-08-21 04:17:24 America/Los_Angeles", 696 | "web_order_line_item_id": "1000000030335737", 697 | "is_trial_period": "false" 698 | }, 699 | { 700 | "quantity": "1", 701 | "product_id": "weekly", 702 | "transaction_id": "1000000168567624", 703 | "original_transaction_id": "1000000166965150", 704 | "purchase_date": "2015-08-21 11:17:24 Etc/GMT", 705 | "purchase_date_ms": "1440155844000", 706 | "purchase_date_pst": "2015-08-21 04:17:24 America/Los_Angeles", 707 | "original_purchase_date": "2015-08-21 11:16:25 Etc/GMT", 708 | "original_purchase_date_ms": "1440155785000", 709 | "original_purchase_date_pst": "2015-08-21 04:16:25 America/Los_Angeles", 710 | "expires_date": "2015-08-21 11:20:24 Etc/GMT", 711 | "expires_date_ms": "1440156024000", 712 | "expires_date_pst": "2015-08-21 04:20:24 America/Los_Angeles", 713 | "web_order_line_item_id": "1000000030335747", 714 | "is_trial_period": "false" 715 | } 716 | ], 717 | "latest_receipt": "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" 718 | } 719 | --------------------------------------------------------------------------------