Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the library to include pending renewal info. #70

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/candy_check/app_store.rb
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
90 changes: 90 additions & 0 deletions lib/candy_check/app_store/pending_renewal_info.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/candy_check/app_store/receipt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def item_id
end

# The quantity of the product
# @return [Fixnum]
# @return [Integer]
def quantity
read_integer('quantity')
end
Expand Down
10 changes: 8 additions & 2 deletions lib/candy_check/app_store/receipt_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ class ReceiptCollection
# @return [Array<Receipt>]
attr_reader :receipts

# Pending renewal info for each product ID
# @return [Array<PendingRenewalInfo>]
attr_reader :pending_renewal_infos

# Initializes a new instance which bases on a JSON result
# from Apple's verification server
# @param attributes [Array<Hash>] raw data from Apple's server
def initialize(attributes)
# @param attributes [Array<Hash>] raw receipt data from Apple's server
# @param pending_renewal_infos [Array<Hash>] 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
Expand Down
11 changes: 8 additions & 3 deletions lib/candy_check/app_store/subscription_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ 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
end

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?
Expand Down
6 changes: 3 additions & 3 deletions lib/candy_check/app_store/verification_failure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ 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
attr_reader :message

# 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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ 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

# 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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/candy_check/play_store/verification_failure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(error)
end

# The code of the failure
# @return [Fixnum]
# @return [Integer]
def code
Integer(error.status_code)
rescue
Expand Down
67 changes: 67 additions & 0 deletions spec/app_store/pending_renewal_info_spec.rb
Original file line number Diff line number Diff line change
@@ -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
27 changes: 24 additions & 3 deletions spec/app_store/receipt_collection_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/app_store/verifier_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down