Skip to content

Commit

Permalink
Add AppStore receipt representation
Browse files Browse the repository at this point in the history
  • Loading branch information
jnbt committed Jan 16, 2015
1 parent dc979b6 commit 4e3cd0c
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 1 deletion.
5 changes: 5 additions & 0 deletions lib/candy_check.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'candy_check/version'
require 'candy_check/config'
require 'candy_check/app_store'

# Module to check and verify in-app receipts
module CandyCheck
Expand All @@ -10,7 +11,11 @@ def self.config
end

# Configure this module
# @yield [config] Allows changing the config
# @yieldparam config [Config]
# @return [Config]
def self.configure
yield config
config
end
end
7 changes: 7 additions & 0 deletions lib/candy_check/app_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'candy_check/app_store/receipt'

module CandyCheck
# Module to request and verify a AppStore receipt
module AppStore
end
end
94 changes: 94 additions & 0 deletions lib/candy_check/app_store/receipt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
module CandyCheck
module AppStore
# Describes a successful response from the AppStore verification server
class Receipt
attr_reader :attributes

# Initializes a new instance which bases on a JSON result
# from Apple's verification server
# @param attributes [Hash]
def initialize(attributes)
@attributes = attributes
end

# In most cases a receipt is a valid transaction except when the
# transaction was canceled.
# @return [Boolean]
def valid?
!has?('cancellation_date')
end

# The receipt's transaction id
# @return [String]
def transaction_id
read('transaction_id')
end

# The receipt's original transaction id which might differ from
# the transaction id for restored products
# @return [String]
def original_transaction_id
read('original_transaction_id')
end

# The version number for the app
# @return [String]
def app_version
read('bvrs')
end

# The app's bundle identifier
# @return [String]
def bundle_identifier
read('bid')
end

# The app's item id of the product
# @return [String]
def item_id
read('item_id')
end

# The quantity of the product
# @return [Fixnum]
def quantity
read('quantity').to_i
end

# The purchase date
# @return [DateTime]
def purchase_date
read_date('purchase_date')
end

# The original purchase date which might differ from the
# actual purchase date for restored products
# @return [DateTime]
def original_purchase_date
read_date('original_purchase_date')
end

# The date of when Apple has canceled this transaction.
# From Apple's documentation: "Treat a canceled receipt
# the same as if no purchase had ever been made."
# @return [DateTime]
def cancellation_date
read_date('cancellation_date')
end

private

def read(field)
attributes[field]
end

def has?(field)
attributes.key?(field)
end

def read_date(field)
(val = read(field)) && DateTime.parse(val)
end
end
end
end
92 changes: 92 additions & 0 deletions spec/app_store/receipt_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require 'spec_helper'

describe CandyCheck::AppStore::Receipt do
subject { CandyCheck::AppStore::Receipt.new(attributes) }

let(:attributes) do
{
'original_purchase_date_pst' => '2015-01-08 03:40:46' \
' America/Los_Angeles',
'purchase_date_ms' => '1420803646868',
'unique_identifier' => 'some_uniq_identifier_from_apple' \
'_for_this',
'original_transaction_id' => 'some_original_transaction_id',
'bvrs' => '2.0',
'transaction_id' => 'some_transaction_id',
'quantity' => '1',
'unique_vendor_identifier' => '00000000-1111-2222-3333-' \
'444444444444',
'item_id' => 'some_item_id',
'product_id' => 'some_product',
'purchase_date' => '2015-01-09 11:40:46 Etc/GMT',
'original_purchase_date' => '2015-01-08 11:40:46 Etc/GMT',
'purchase_date_pst' => '2015-01-09 03:40:46' \
' America/Los_Angeles',
'bid' => 'some.test.app',
'original_purchase_date_ms' => '1420717246868'
}
end

describe 'valid transaction' do
it 'is valid' do
subject.valid?.must_be_true
end

it 'returns the item\'s id' do
subject.item_id.must_equal 'some_item_id'
end

it 'returns the quantity' do
subject.quantity.must_equal 1
end

it 'returns the app version' do
subject.app_version.must_equal '2.0'
end

it 'returns the bundle identifier' do
subject.bundle_identifier.must_equal 'some.test.app'
end

it 'returns the purchase date' do
expected = DateTime.new(2015, 1, 9, 11, 40, 46)
subject.purchase_date.must_equal expected
end

it 'returns the original purchase date' do
expected = DateTime.new(2015, 1, 8, 11, 40, 46)
subject.original_purchase_date.must_equal expected
end

it 'returns the transaction id' do
subject.transaction_id.must_equal 'some_transaction_id'
end

it 'returns the original transaction id' do
subject.original_transaction_id.must_equal 'some_original_transaction_id'
end

it 'return nil for cancellation date' do
subject.cancellation_date.must_be_nil
end

it 'returns raw attributes' do
subject.attributes.must_be_same_as attributes
end
end

describe 'valid transaction' do
before do
attributes['cancellation_date'] = '2015-01-12 11:40:46 Etc/GMT'
end

it 'isn\'t valid' do
subject.valid?.must_be_false
end

it 'return nil for cancellation date' do
expected = DateTime.new(2015, 1, 12, 11, 40, 46)
subject.cancellation_date.must_equal expected
end
end
end
3 changes: 2 additions & 1 deletion spec/candy_check_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
subject.config.must_be_instance_of CandyCheck::Config
end
it 'allows configuration' do
subject.configure do |config|
c = subject.configure do |config|
config.test_value = 1
end
subject.config.test_value.must_equal 1
c.must_be_same_as(subject.config)
end
end
21 changes: 21 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,24 @@
require 'candy_check'

require 'minitest/autorun'

module MiniTest
module Assertions
# The first parameter must be ```true```, not coercible to true.
def assert_true(obj, msg = nil)
msg = message(msg) { "<true> expected but was #{mu_pp obj}" }
assert obj == true, msg
end

# The first parameter must be ```false```, not just coercible to false.
def assert_false(obj, msg = nil)
msg = message(msg) { "<false> expected but was #{mu_pp obj}" }
assert obj == false, msg
end
end

module Expectations
infect_an_assertion :assert_true, :must_be_true, :unary
infect_an_assertion :assert_false, :must_be_false, :unary
end
end

0 comments on commit 4e3cd0c

Please sign in to comment.