diff --git a/lib/candy_check/app_store.rb b/lib/candy_check/app_store.rb index e4f1586..6c7f6ea 100644 --- a/lib/candy_check/app_store.rb +++ b/lib/candy_check/app_store.rb @@ -1,5 +1,6 @@ require 'candy_check/app_store/client' require 'candy_check/app_store/config' +require 'candy_check/app_store/pending_renewal_info' require 'candy_check/app_store/receipt' require 'candy_check/app_store/receipt_collection' require 'candy_check/app_store/verification' diff --git a/lib/candy_check/app_store/pending_renewal_info.rb b/lib/candy_check/app_store/pending_renewal_info.rb new file mode 100644 index 0000000..3c6ae94 --- /dev/null +++ b/lib/candy_check/app_store/pending_renewal_info.rb @@ -0,0 +1,90 @@ +module CandyCheck + module AppStore + + # Encapsulates Apple's pending renewal info included with renewable subscriptions. + class PendingRenewalInfo + include Utils::AttributeReader + + # @return [Hash] the raw attributes returned from the server + attr_reader :attributes + + # Initializes a new instance with a hash mapping to the attributes. + # @param [Hash] the attributes for the pending renewal info + def initialize(attributes) + @attributes = attributes + end + + # productIdentifier property of the product that the customer’s subscription renews. + # @return [String] + def auto_renew_product_id + read('auto_renew_product_id') + end + + # The renewal status for the auto-renewable subscription. + # @return [Integer] + def auto_renew_status + read_integer('auto_renew_status') + end + + # The reason a subscription expired. + # @return [Integer] + def expiration_intent + read_integer('expiration_intent') + end + + # The grace period expiration time. + # @return [DateTime] + def grace_period_expires_date + read_datetime_from_string('grace_period_expires_date') + end + + # The grace period expiration time in UNIX epoch time format, in milliseconds. + # @return [String] + def grace_period_expires_date_ms + read_integer('grace_period_expires_date_ms') + end + + # The grace period expiration time in PST. + # @return [DateTime] + def grace_period_expires_date_pst + read_datetime_from_string('grace_period_expires_date_pst') + end + + # Whether an auto-renewable subscription is in the billing retry period. + # @return [Integer] + def is_in_billing_retry_period + read_integer('is_in_billing_retry_period') + end + + # The offer-reference name of the subscription offer code that the customer redeemed. + # @return [String] + def offer_code_ref_name + read('offer_code_ref_name') + end + + # The transaction identifier of the original purchase. + # @return [String] + def original_transaction_id + read('original_transaction_id') + end + + # The price consent status for a subscription price increase. + # @return [Integer] + def price_consent_status + read_integer('price_consent_status') + end + + # The unique identifier of the product purchased. + # @return [String] + def product_id + read('product_id') + end + + # The identifier of the promotional offer for an auto-renewable subscription that the user redeemed. + # @return [String] + def promotional_offer_id + read('promotional_offer_id') + end + end + end +end \ No newline at end of file diff --git a/lib/candy_check/app_store/receipt.rb b/lib/candy_check/app_store/receipt.rb index 423723d..375843b 100644 --- a/lib/candy_check/app_store/receipt.rb +++ b/lib/candy_check/app_store/receipt.rb @@ -59,7 +59,7 @@ def item_id end # The quantity of the product - # @return [Fixnum] + # @return [Integer] def quantity read_integer('quantity') end diff --git a/lib/candy_check/app_store/receipt_collection.rb b/lib/candy_check/app_store/receipt_collection.rb index 2cd21df..c3bfc8a 100644 --- a/lib/candy_check/app_store/receipt_collection.rb +++ b/lib/candy_check/app_store/receipt_collection.rb @@ -6,13 +6,19 @@ class ReceiptCollection # @return [Array] attr_reader :receipts + # Pending renewal info for each product ID + # @return [Array] + attr_reader :pending_renewal_infos + # Initializes a new instance which bases on a JSON result # from Apple's verification server - # @param attributes [Array] raw data from Apple's server - def initialize(attributes) + # @param attributes [Array] raw receipt data from Apple's server + # @param pending_renewal_infos [Array] raw pending renewal data from Apple's server + def initialize(attributes, pending_renewal_infos) @receipts = attributes.map {|r| Receipt.new(r) }.sort{ |a, b| a.purchase_date - b.purchase_date } + @pending_renewal_infos = pending_renewal_infos.map { |p| PendingRenewalInfo.new(p) } end # Check if the latest expiration date is passed diff --git a/lib/candy_check/app_store/subscription_verification.rb b/lib/candy_check/app_store/subscription_verification.rb index 2e9a615..a065032 100644 --- a/lib/candy_check/app_store/subscription_verification.rb +++ b/lib/candy_check/app_store/subscription_verification.rb @@ -24,7 +24,7 @@ def initialize( def call! verify! if valid? - build_collection(@response['latest_receipt_info']) + build_collection(@response['latest_receipt_info'], @response['pending_renewal_info'].to_a) else VerificationFailure.fetch(@response['status']) end @@ -32,13 +32,18 @@ def call! private - def build_collection(latest_receipt_info) + def build_collection(latest_receipt_info, pending_renewal_info) unless @product_ids.nil? latest_receipt_info = latest_receipt_info.select do |info| @product_ids.include?(info['product_id']) end + if pending_renewal_info + pending_renewal_info = pending_renewal_info.select do |info| + @product_ids.include?(info['product_id']) + end + end end - ReceiptCollection.new(latest_receipt_info) + ReceiptCollection.new(latest_receipt_info, pending_renewal_info) end def valid? diff --git a/lib/candy_check/app_store/verification_failure.rb b/lib/candy_check/app_store/verification_failure.rb index abc58cb..cead4fd 100644 --- a/lib/candy_check/app_store/verification_failure.rb +++ b/lib/candy_check/app_store/verification_failure.rb @@ -2,7 +2,7 @@ module CandyCheck module AppStore # Represents a failing call against the verification server class VerificationFailure - # @return [Fixnum] the code of the failure + # @return [Integer] the code of the failure attr_reader :code # @return [String] the message of the failure @@ -10,7 +10,7 @@ class VerificationFailure # Initializes a new instance which bases on a JSON result # from Apple servers - # @param code [Fixnum] + # @param code [Integer] # @param message [String] def initialize(code, message) @code = code @@ -20,7 +20,7 @@ def initialize(code, message) class << self # Gets a known failure or build an unknown failure # without description - # @param code [Fixnum] + # @param code [Integer] # @return [VerificationFailure] def fetch(code) known.fetch(code) do diff --git a/lib/candy_check/play_store/product_purchases/product_purchase.rb b/lib/candy_check/play_store/product_purchases/product_purchase.rb index 3270ad8..2c5321a 100644 --- a/lib/candy_check/play_store/product_purchases/product_purchase.rb +++ b/lib/candy_check/play_store/product_purchases/product_purchase.rb @@ -26,7 +26,7 @@ def initialize(product_purchase) # The purchase state of the order. Possible values are: # * 0: Purchased # * 1: Cancelled - # @return [Fixnum] + # @return [Integer] def purchase_state @product_purchase.purchase_state end @@ -34,7 +34,7 @@ def purchase_state # The consumption state of the inapp product. Possible values are: # * 0: Yet to be consumed # * 1: Consumed - # @return [Fixnum] + # @return [Integer] def consumption_state @product_purchase.consumption_state end @@ -60,7 +60,7 @@ def order_id # The time the product was purchased, in milliseconds since the # epoch (Jan 1, 1970) - # @return [Fixnum] + # @return [Integer] def purchase_time_millis @product_purchase.purchase_time_millis end diff --git a/lib/candy_check/play_store/verification_failure.rb b/lib/candy_check/play_store/verification_failure.rb index 7a0035b..181b85d 100644 --- a/lib/candy_check/play_store/verification_failure.rb +++ b/lib/candy_check/play_store/verification_failure.rb @@ -15,7 +15,7 @@ def initialize(error) end # The code of the failure - # @return [Fixnum] + # @return [Integer] def code Integer(error.status_code) rescue diff --git a/spec/app_store/pending_renewal_info_spec.rb b/spec/app_store/pending_renewal_info_spec.rb new file mode 100644 index 0000000..4256712 --- /dev/null +++ b/spec/app_store/pending_renewal_info_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe CandyCheck::AppStore::PendingRenewalInfo do + subject { CandyCheck::AppStore::PendingRenewalInfo.new(attributes) } + + let(:attributes) do + { + "auto_renew_product_id" => "some_product", + "auto_renew_status" => "1", + "expiration_intent" => "0", + "grace_period_expires_date" => '2015-01-09 11:40:46 Etc/GMT', + "grace_period_expires_date_ms" => '1420717246868', + "grace_period_expires_date_pst" => '2015-01-09 03:40:46 America/Los_Angeles', + "is_in_billing_retry_period" => "0", + "offer_code_ref_name" => "some_offer_code_ref_name", + "original_transaction_id" => "some_original_transaction_id", + "price_consent_status" => "0", + "product_id" => "some_product", + "promotional_offer_id" => "some_promotional_offer_id", + } + end + + it 'returns the auto renew product_id' do + _(subject.auto_renew_product_id).must_equal 'some_product' + end + + it 'returns the auto renew status' do + _(subject.auto_renew_status).must_equal 1 + end + + it 'returns the expiration intent' do + _(subject.expiration_intent).must_equal 0 + end + + it 'returns the grace period expiration date' do + expected = DateTime.new(2015, 1, 9, 11, 40, 46) + _(subject.grace_period_expires_date).must_equal expected + end + + it 'returns whether item is in billing retry period' do + _(subject.is_in_billing_retry_period).must_equal 0 + end + + it 'returns the offer code reference name' do + _(subject.offer_code_ref_name).must_equal 'some_offer_code_ref_name' + end + + it 'returns the original transaction id' do + _(subject.original_transaction_id).must_equal 'some_original_transaction_id' + end + + it 'returns the price consent status' do + _(subject.price_consent_status).must_equal 0 + end + + it 'returns the product id' do + _(subject.product_id).must_equal 'some_product' + end + + it 'returns the promotional offer id' do + _(subject.promotional_offer_id).must_equal 'some_promotional_offer_id' + end + + it 'returns raw attributes' do + _(subject.attributes).must_be_same_as attributes + end +end diff --git a/spec/app_store/receipt_collection_spec.rb b/spec/app_store/receipt_collection_spec.rb index b96d696..bfc091d 100644 --- a/spec/app_store/receipt_collection_spec.rb +++ b/spec/app_store/receipt_collection_spec.rb @@ -1,7 +1,24 @@ require 'spec_helper' describe CandyCheck::AppStore::ReceiptCollection do - subject { CandyCheck::AppStore::ReceiptCollection.new(attributes) } + subject { CandyCheck::AppStore::ReceiptCollection.new(attributes, pending_renewal_infos) } + + let(:pending_renewal_infos) do + [{ + "auto_renew_product_id" => "some_product", + "auto_renew_status" => "1", + "expiration_intent" => "0", + "grace_period_expires_date" => '2015-01-09 11:40:46 Etc/GMT', + "grace_period_expires_date_ms" => '1420717246868', + "grace_period_expires_date_pst" => '2015-01-09 03:40:46 America/Los_Angeles', + "is_in_billing_retry_period" => "0", + "offer_code_ref_name" => "some_offer_code_ref_name", + "original_transaction_id" => "some_original_transaction_id", + "price_consent_status" => "0", + "product_id" => "some_product", + "promotional_offer_id" => "some_promotional_offer_id", + }] + end describe 'overdue subscription' do let(:attributes) do @@ -28,7 +45,7 @@ it 'has positive overdue days' do overdue = subject.overdue_days - _(overdue).must_be_instance_of Fixnum + _(overdue).must_be_instance_of Integer assert overdue > 0 end @@ -37,11 +54,15 @@ _(subject.expires_at).must_equal expected end - it 'is expired? at same pointin time' do + it 'is expired? at same point in time' do Timecop.freeze(Time.utc(2015, 4, 15, 12, 52, 40)) do _(subject.expired?).must_be_true end end + + it 'has an array of CandyCheck::AppStore::PendingRenewalInfo objects' do + _(subject.pending_renewal_infos.first).must_be_instance_of CandyCheck::AppStore::PendingRenewalInfo + end end describe 'unordered receipts' do diff --git a/spec/app_store/verifier_spec.rb b/spec/app_store/verifier_spec.rb index f96eb10..102c43d 100644 --- a/spec/app_store/verifier_spec.rb +++ b/spec/app_store/verifier_spec.rb @@ -9,7 +9,7 @@ let(:data) { 'some_data' } let(:secret) { 'some_secret' } let(:receipt) { CandyCheck::AppStore::Receipt.new({}) } - let(:receipt_collection) { CandyCheck::AppStore::ReceiptCollection.new({}) } + let(:receipt_collection) { CandyCheck::AppStore::ReceiptCollection.new({}, {}) } let(:production_endpoint) do 'https://buy.itunes.apple.com/verifyReceipt' end