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

[AppStore, Subscription] Add verification returning both receipt and receipt collection #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 6 additions & 4 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ AllCops:
- 'vendor/**/*'
Style/Documentation:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/NumericPredicate:
Enabled: false
Metrics/BlockLength:
Exclude:
- 'spec/**/*'
Metrics/LineLength:
Exclude:
- 'spec/**/*'
Lint/UnifiedInteger:
Enabled: false
Style/PercentLiteralDelimiters:
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

language: ruby
sudo: false
cache: bundler
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

source 'https://rubygems.org'

# Specify your gem's dependencies in candy_check.gemspec
Expand Down
1 change: 1 addition & 0 deletions Rakefile
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env rake
# frozen_string_literal: true

require 'bundler/gem_tasks'
require 'rake/testtask'
Expand Down
1 change: 1 addition & 0 deletions candy_check.gemspec
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# coding: utf-8
# frozen_string_literal: true

lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'candy_check/version'
require 'candy_check/utils'
require 'candy_check/app_store'
Expand Down
4 changes: 4 additions & 0 deletions lib/candy_check/app_store.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# frozen_string_literal: true

require 'candy_check/app_store/client'
require 'candy_check/app_store/config'
require 'candy_check/app_store/receipt'
require 'candy_check/app_store/receipt_collection'
require 'candy_check/app_store/verification'
require 'candy_check/app_store/subscription_verification'
require 'candy_check/app_store/full_subscription_verification'
require 'candy_check/app_store/subscription_receipt'
require 'candy_check/app_store/verification_failure'
require 'candy_check/app_store/verifier'

Expand Down
4 changes: 3 additions & 1 deletion lib/candy_check/app_store/client.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'multi_json'
require 'net/http'

Expand All @@ -7,7 +9,7 @@ module AppStore
# servers (either sandbox or production).
class Client
# Mimetype for JSON objects
JSON_MIME_TYPE = 'application/json'.freeze
JSON_MIME_TYPE = 'application/json'

# Initialize a new client bound to an endpoint
# @param endpoint_url [String]
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/app_store/config.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Configure the verifier
Expand Down
33 changes: 33 additions & 0 deletions lib/candy_check/app_store/full_subscription_verification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Verifies a receipt and latest_receipt_info block
# The call return either a {SubscriptionReceipt} or a {VerificationFailure}
class FullSubscriptionVerification < CandyCheck::AppStore::Verification
# Performs the verification against the remote server
# @return [SubscriptionReceipt] if successful
# @return [VerificationFailure] otherwise
def call!
verify!
return VerificationFailure.fetch(@response['status']) unless valid?
subscription_receipt
end

private

def subscription_receipt
receipt = Receipt.new(@response['receipt'])
receipt_collection = ReceiptCollection.new(
@response['latest_receipt_info']
)

SubscriptionReceipt.new(receipt, receipt_collection)
end

def valid?
super && @response['latest_receipt_info']
end
end
end
end
2 changes: 2 additions & 0 deletions lib/candy_check/app_store/receipt.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Describes a successful response from the AppStore verification server
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/app_store/receipt_collection.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Store multiple {Receipt}s in order to perform collective operation on them
Expand Down
25 changes: 25 additions & 0 deletions lib/candy_check/app_store/subscription_receipt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Describes a successful response from the AppStore verification server
class SubscriptionReceipt
attr_reader :receipt, :receipt_collection

# Initializes a new instance
def initialize(receipt, receipt_collection)
@receipt = receipt
@receipt_collection = receipt_collection
end

def transactions
@receipt_collection.receipts
end

def valid?
@receipt.is_a?(CandyCheck::AppStore::Receipt) &&
@receipt_collection.is_a?(CandyCheck::AppStore::ReceiptCollection)
end
end
end
end
2 changes: 2 additions & 0 deletions lib/candy_check/app_store/subscription_verification.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Verifies a latest_receipt_info block against a verification server.
Expand Down
8 changes: 7 additions & 1 deletion lib/candy_check/app_store/verification.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Verifies a receipt block against a verification server.
Expand Down Expand Up @@ -37,8 +39,12 @@ def call!

private

def response_status_ok?
@response['status'] == STATUS_OK
end

def valid?
@response && @response['status'] == STATUS_OK && @response['receipt']
@response && response_status_ok? && @response['receipt']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a bit like response_status_ok? is not self-contained if you have to check @response before you use it. Maybe it would be nicer with two black boxes: response_status_ok? and response_has_receipt? which both take care of validating all that they need. This would mean duplicating the @response check, but I think that's acceptable, or if the response is only set once, then you could even cache/memoize this in a has_response? method

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it 👍

end

def verify!
Expand Down
4 changes: 4 additions & 0 deletions lib/candy_check/app_store/verification_failure.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Represents a failing call against the verification server
Expand Down Expand Up @@ -64,6 +66,8 @@ def freeze!
add 21_008, 'This receipt is from the production environment, but it' \
' was sent to the test environment for verification.' \
' Send it to the production environment instead.'
add 21_009, 'There was a problem with the (internal) receipt validation' \
' process. Please try again.'
freeze!
end
end
Expand Down
16 changes: 14 additions & 2 deletions lib/candy_check/app_store/verifier.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# frozen_string_literal: true

module CandyCheck
module AppStore
# Verifies receipts against the verification servers.
# The call return either an {Receipt} or a {VerificationFailure}
class Verifier
# HTTPS endpoint for production receipts
PRODUCTION_ENDPOINT = 'https://buy.itunes.apple.com/verifyReceipt'.freeze
PRODUCTION_ENDPOINT = 'https://buy.itunes.apple.com/verifyReceipt'
# HTTPS endpoint for sandbox receipts
SANDBOX_ENDPOINT = 'https://sandbox.itunes.apple.com/verifyReceipt'.freeze
SANDBOX_ENDPOINT = 'https://sandbox.itunes.apple.com/verifyReceipt'
# Status code from production endpoint when receiving a sandbox
# receipt which occurs during the app's review process
REDIRECT_TO_SANDBOX_CODE = 21_007
Expand Down Expand Up @@ -44,6 +46,16 @@ def verify_subscription(receipt_data, secret = nil)
fetch_receipt_information(receipt_data, secret)
end

# Calls a subscription verification for the given input
# @param receipt_data [String] the raw data to be verified
# @param secret [string] the optional shared secret
# @return [SubscriptionReceipt] if successful
# @return [VerificationFailure] otherwise
def verify_subscription_with_full_response(receipt_data, secret = nil)
@verifier = FullSubscriptionVerification

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's weird to have @verifier as instance state. It introduces coupling in the order of invocation of the public methods here, which can cause race conditions if run in parallel.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. However I would like to keep it consistent with the rest of the code.

fetch_receipt_information(receipt_data, secret)
end

private

def fetch_receipt_information(receipt_data, secret = nil)
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'candy_check/cli/app'
require 'candy_check/cli/commands'
require 'candy_check/cli/out'
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/app.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'thor'

module CandyCheck
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/commands.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'candy_check/cli/commands/base'
require 'candy_check/cli/commands/app_store'
require 'candy_check/cli/commands/play_store'
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/commands/app_store.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module CLI
module Commands
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/commands/base.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module CLI
module Commands
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/commands/play_store.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module CLI
module Commands
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/commands/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module CLI
module Commands
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/cli/out.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'pp'

module CandyCheck
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'google/api_client'

require 'candy_check/play_store/discovery_repository'
Expand Down
10 changes: 6 additions & 4 deletions lib/candy_check/play_store/client.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# A client which uses the official Google API SDK to authenticate
Expand All @@ -15,13 +17,13 @@ class Client
class DiscoveryError < RuntimeError; end

# API endpoint
API_URL = 'https://accounts.google.com/o/oauth2/token'.freeze
API_URL = 'https://accounts.google.com/o/oauth2/token'
# API scope for Android services
API_SCOPE = 'https://www.googleapis.com/auth/androidpublisher'.freeze
API_SCOPE = 'https://www.googleapis.com/auth/androidpublisher'
# API discovery namespace
API_DISCOVER = 'androidpublisher'.freeze
API_DISCOVER = 'androidpublisher'
# API version
API_VERSION = 'v2'.freeze
API_VERSION = 'v2'

# Initializes a client using a configuration.
# @param config [ClientConfig]
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/config.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Configure the usage of the official Google API SDK client
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/discovery_repository.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# A file-based repository to cache a local copy of the Google API
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/receipt.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Describes a successful response from the Google verification server
Expand Down
7 changes: 5 additions & 2 deletions lib/candy_check/play_store/subscription.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Describes a successfully validated subscription
Expand Down Expand Up @@ -25,17 +27,18 @@ def initialize(attributes)

# Check if the expiration date is passed
# @return [bool]
# rubocop:disable Style/NumericPredicate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use .positive? ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not supported in older versions.

def expired?
overdue_days > 0
end
# rubocop:enable Style/NumericPredicate

# Check if in trial. This is actually not given by Google, but we assume
# that it is a trial going on if the paid amount is 0 and
# renewal is activated.
# @return [bool]
def trial?
price_is_zero = price_amount_micros == 0
price_is_zero && payment_received?
price_amount_micros.zero? && payment_received?
end

# see if payment is ok
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/subscription_verification.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Verifies a purchase token against the Google API
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/verification.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Verifies a purchase token against the Google API
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/verification_failure.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Represents a failing call against the Google API server
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/play_store/verifier.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module PlayStore
# Verifies purchase tokens against the Google API.
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/utils.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# frozen_string_literal: true

require 'candy_check/utils/attribute_reader'
require 'candy_check/utils/config'
2 changes: 2 additions & 0 deletions lib/candy_check/utils/attribute_reader.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'date'

module CandyCheck
Expand Down
2 changes: 2 additions & 0 deletions lib/candy_check/utils/config.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CandyCheck
module Utils
# Very basic base implementation to store and validate a configuration
Expand Down
4 changes: 3 additions & 1 deletion lib/candy_check/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# frozen_string_literal: true

module CandyCheck
# The current gem's version
VERSION = '0.1.1'.freeze
VERSION = '0.1.2'
end
Loading