candy_check 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +6 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +157 -0
  9. data/Rakefile +15 -0
  10. data/bin/cc_appstore +90 -0
  11. data/bin/cc_playstore +119 -0
  12. data/candy_check.gemspec +31 -0
  13. data/lib/candy_check/app_store/client.rb +57 -0
  14. data/lib/candy_check/app_store/config.rb +30 -0
  15. data/lib/candy_check/app_store/receipt.rb +83 -0
  16. data/lib/candy_check/app_store/verification.rb +49 -0
  17. data/lib/candy_check/app_store/verification_failure.rb +60 -0
  18. data/lib/candy_check/app_store/verifier.rb +69 -0
  19. data/lib/candy_check/app_store.rb +12 -0
  20. data/lib/candy_check/play_store/client.rb +102 -0
  21. data/lib/candy_check/play_store/config.rb +51 -0
  22. data/lib/candy_check/play_store/discovery_repository.rb +33 -0
  23. data/lib/candy_check/play_store/receipt.rb +81 -0
  24. data/lib/candy_check/play_store/verification.rb +46 -0
  25. data/lib/candy_check/play_store/verification_failure.rb +30 -0
  26. data/lib/candy_check/play_store/verifier.rb +52 -0
  27. data/lib/candy_check/play_store.rb +15 -0
  28. data/lib/candy_check/utils/attribute_reader.rb +30 -0
  29. data/lib/candy_check/utils/config.rb +40 -0
  30. data/lib/candy_check/utils.rb +2 -0
  31. data/lib/candy_check/version.rb +4 -0
  32. data/lib/candy_check.rb +8 -0
  33. data/spec/app_store/client_spec.rb +55 -0
  34. data/spec/app_store/config_spec.rb +41 -0
  35. data/spec/app_store/receipt_spec.rb +92 -0
  36. data/spec/app_store/verifcation_failure_spec.rb +28 -0
  37. data/spec/app_store/verification_spec.rb +66 -0
  38. data/spec/app_store/verifier_spec.rb +110 -0
  39. data/spec/candy_check_spec.rb +9 -0
  40. data/spec/fixtures/api_cache.dump +1 -0
  41. data/spec/fixtures/play_store/api_cache.dump +1 -0
  42. data/spec/fixtures/play_store/auth_failure.txt +18 -0
  43. data/spec/fixtures/play_store/auth_success.txt +20 -0
  44. data/spec/fixtures/play_store/discovery.txt +2841 -0
  45. data/spec/fixtures/play_store/dummy.p12 +0 -0
  46. data/spec/fixtures/play_store/empty.txt +17 -0
  47. data/spec/fixtures/play_store/products_failure.txt +29 -0
  48. data/spec/fixtures/play_store/products_success.txt +22 -0
  49. data/spec/play_store/client_spec.rb +104 -0
  50. data/spec/play_store/config_spec.rb +96 -0
  51. data/spec/play_store/discovery_respository_spec.rb +31 -0
  52. data/spec/play_store/receipt_spec.rb +88 -0
  53. data/spec/play_store/verification_failure_spec.rb +35 -0
  54. data/spec/play_store/verification_spec.rb +80 -0
  55. data/spec/play_store/verifier_spec.rb +95 -0
  56. data/spec/spec_helper.rb +35 -0
  57. data/spec/support/with_fixtures.rb +9 -0
  58. data/spec/support/with_temp_file.rb +23 -0
  59. metadata +270 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 84e1244d6bb30895e3ee8ca24c694c906f75594b
4
+ data.tar.gz: ffb09d52c05badfde0b6e6b4de556d35c6424eaa
5
+ SHA512:
6
+ metadata.gz: fb4c732e8cf98ddd4c1678a817ce73e4a3301b42aff1482829cdfd6d010e2c015125c28fcdbf74b31b9e141a57f7788ad208282b9a15a9a12a832a1b3a86fa9c
7
+ data.tar.gz: ae4c8f3878780f0610107f154e46962892472dee8ede6f7d10a84cc6efc53418bb0377a635dc58b827336b52340871ca7d0e2fdaf6071df919da6f24a2587fd1
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ /Gemfile.lock
2
+ /pkg/
3
+ /coverage/
4
+ .yardoc
5
+ doc
data/.rubocop.yml ADDED
@@ -0,0 +1,6 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/support/*'
4
+ - 'bin/*'
5
+ Style/Documentation:
6
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.5
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - '2.2'
5
+ - '2.1'
6
+ - '2.0'
7
+ - '1.9.3'
8
+ - ruby-head
9
+ - rbx-2
10
+ - jruby-19mode
11
+ - jruby-head
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
15
+ - rvm: jruby-head
16
+ fast_finish: true
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in candy_check.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jonas Thiel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # candy_check
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/candy_check.svg)](http://badge.fury.io/rb/candy_check)
4
+ [![Build Status](https://travis-ci.org/jnbt/candy_check.svg?branch=master)](https://travis-ci.org/jnbt/candy_check)
5
+ [![Coverage Status](https://coveralls.io/repos/jnbt/candy_check/badge.svg?branch=master)](https://coveralls.io/r/jnbt/candy_check?branch=master)
6
+ [![Inline docs](http://inch-ci.org/github/jnbt/candy_check.svg?branch=master)](http://inch-ci.org/github/jnbt/candy_check)
7
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg?style=flat)](http://www.rubydoc.info/github/jnbt/candy_check/master)
8
+
9
+ Check and verify in-app receipts from the AppStore and the PlayStore.
10
+
11
+ ## Installation
12
+
13
+ ```Bash
14
+ gem install candy_check
15
+ ```
16
+
17
+ ## Introduction
18
+
19
+ This gem tries to simplify the process of server-side in-app purchase validation for Apple's AppStore and
20
+ Google's PlayStore.
21
+
22
+ ### AppStore
23
+
24
+ If you have set up an iOS app and it's in-app items correctly and the in-app store is working your app should receive a
25
+ `SKPaymentTransaction`. Currently this gem assumes that you use the old [`transactionReceipt`](https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKPaymentTransaction_Class/index.html#//apple_ref/occ/instp/SKPaymentTransaction/transactionReceipt)
26
+ which is returned per transaction. The `transactionReceipt` is a base64 encoded binary blob which you should send to your
27
+ server for the validation process.
28
+
29
+ To validate a receipt one normally has to choose between the two different endpoints "production" and "sandbox" which are provided from
30
+ [Apple](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1).
31
+ During development your app gets receipts from the sandbox while when released from the production system. A special case is the
32
+ review process because the review team uses the release version of your app but processes payment against the sandbox.
33
+ Only for receipts that contain auto-renewable subscriptions you need your app's shared secret (a hexadecimal string),
34
+
35
+ Please keep in mind that you have to use test user account from the iTunes connect portal to test in-app purchases during
36
+ your app's development.
37
+
38
+ ### PlayStore
39
+
40
+ Google's PlayStore has different kind of server-to-server API to check purchases and requires that you register a so
41
+ called "[service account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount)". You have to register a
42
+ new account by yourself, export the generated certificate file and grant the correct permissions to the account for
43
+ your app using the [Google Developer Console](https://console.developers.google.com).
44
+
45
+ Further more this gem uses the [official Ruby SDK](https://github.com/google/google-api-ruby-client) for the API interactions
46
+ which suggest to use a locally cached service discovery. If you don't omit the `cache_file` configuration this is done
47
+ automatically.
48
+
49
+ If you have set up the Android app correctly you should get a [`purchaseToken`](http://developer.android.com/google/play/billing/billing_reference.html#getBuyIntent) per purchased item. You should use this string in combination with `packageName` and `productId`
50
+ to verify the purchase.
51
+
52
+ ## Usage
53
+
54
+ ### AppStore
55
+
56
+ First you should initialize a verifier instance for your application:
57
+
58
+ ```ruby
59
+ config = CandyCheck::AppStore::Config.new(
60
+ environment: :production # or :sandbox
61
+ )
62
+ verifier = CandyCheck::AppStore::Verifier.new(config)
63
+ ```
64
+
65
+ For the AppStore the client should deliver a base64 encoded receipt data string
66
+ which can be verified by using the following call:
67
+
68
+ ```ruby
69
+ verifier.verify(your_receipt_data) # => Receipt or VerificationFailure
70
+ # or by using a shared secret for subscriptions
71
+ verifier.verify(your_receipt_data, your_secret)
72
+ ```
73
+
74
+ Please see the class documenations [`CandyCheck::AppStore::Receipt`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/Receipt) and [`CandyCheck::AppStore::VerificationFailure`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/VerificationFailure) for further details about the responses.
75
+
76
+ ### PlayStore
77
+
78
+ First initialize and **boot** a verifier instance for your application. This loads the API discovery and
79
+ fetches the needed OAuth access token. When configuring a `cache_file` the discovery is loaded (or dumped) to
80
+ this file:
81
+
82
+ ```ruby
83
+ config = CandyCheck::PlayStore::Config.new(
84
+ application_name: 'YourApplication',
85
+ application_version: '1.0',
86
+ issuer: 'abcdefg@developer.gserviceaccount.com',
87
+ key_file: 'local/google.p12',
88
+ key_secret: 'notasecret',
89
+ cache_file: 'tmp/candy_check_play_store_cache'
90
+ )
91
+ verifier = CandyCheck::PlayStore::Verifier.new(config)
92
+ verifier.boot!
93
+ ```
94
+
95
+ For the PlayStore your client should deliver the purchases token, package name and product id:
96
+
97
+ ```ruby
98
+ verifier.verify(package, product_id, token) # => Receipt or VerificationFailure
99
+ ```
100
+
101
+ Please see the class documenations [`CandyCheck::PlayStore::Receipt`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/PlayStore/Receipt) and [`CandyCheck::PlayStore::VerificationFailure`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/PlayStore/VerificationFailure) for further details about the responses.
102
+
103
+ ## CLI
104
+
105
+ This gem ships with two executables to verify in-app purchases directly from your terminal:
106
+
107
+ ### AppStore
108
+
109
+ You only need to specify the base64 encoded receipt:
110
+
111
+ ```bash
112
+ $ cc_appstore --receipt YOUR_RECEIPT_STRING
113
+ ```
114
+
115
+ See all options:
116
+
117
+ ```bash
118
+ $ cc_appstore --help
119
+ ```
120
+
121
+ ### PlayStore
122
+
123
+ For the PlayStore you need to specify at least the issuer, the key file, your package name, the product and the actual
124
+ purchase token:
125
+
126
+ ```bash
127
+ $ cc_playstore --issuer ISSUER_EMAIL --key-file KEY_FILE \
128
+ --package PACKAGE_NAME --product-id PRODUCT_ID --token PURCHASE_TOKEN
129
+ ```
130
+
131
+ See all options:
132
+
133
+ ```bash
134
+ $ cc_playstore --help
135
+ ```
136
+
137
+
138
+ ## Todos
139
+
140
+ * Allow using the combined StoreKit receipt data
141
+ * Find a ways to run integration tests
142
+
143
+ ## Bugs and Issues
144
+
145
+ Please submit them here https://github.com/jnbt/candy_check/issues
146
+
147
+ ## Test
148
+
149
+ Simple run
150
+
151
+ ```Bash
152
+ rake
153
+ ```
154
+
155
+ ## Copyright
156
+
157
+ Copyright © 2015 Jonas Thiel. See LICENSE.txt for details.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+
7
+ Rake::TestTask.new(:spec) do |test|
8
+ test.test_files = FileList['spec/**/*_spec.rb']
9
+ test.libs << 'spec'
10
+ test.verbose = true
11
+ end
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ task default: [:spec, :rubocop]
data/bin/cc_appstore ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'optparse'
5
+ require 'ostruct'
6
+ require 'pp'
7
+ require 'candy_check'
8
+
9
+ class InputParser
10
+ attr_reader :config, :secret, :receipt
11
+
12
+ def initialize
13
+ @config = {
14
+ environment: :production
15
+ }
16
+ configure!
17
+ end
18
+
19
+ def parse!(args)
20
+ @parser.parse!(args)
21
+ help! unless @receipt
22
+ end
23
+
24
+ private
25
+
26
+ def configure!
27
+ @parser = OptionParser.new do |opts|
28
+ @opts = opts
29
+ opts.banner = 'Usage cc_appstore [options]'
30
+ opts.separator ''
31
+ opts.separator 'Required options:'
32
+
33
+ opts.on('-r', '--receipt RECEIPT', 'a base64 encoded receipt data string') do |v|
34
+ @receipt = v
35
+ end
36
+ opts.separator ''
37
+ opts.separator 'Optional options:'
38
+ opts.on('-e', '--environment ENVIRONMENT', 'production (default) or sandbox') do |v|
39
+ config[:environment] = v.to_sym
40
+ end
41
+ opts.on('-s', '--secret SECRET', 'shared secret for auto-renewable subscriptions') do |v|
42
+ @secret = v
43
+ end
44
+ opts.separator ''
45
+ opts.separator 'Further functions:'
46
+ opts.on_tail('-v', '--version', 'Show version') do
47
+ puts CandyCheck::VERSION
48
+ exit
49
+ end
50
+ opts.on_tail('-h', '--help', 'Show help') do
51
+ help!
52
+ end
53
+ end
54
+ end
55
+
56
+ def help!
57
+ puts @opts
58
+ exit
59
+ end
60
+ end
61
+
62
+ class App
63
+ attr_reader :input
64
+
65
+ def initialize
66
+ @input = InputParser.new
67
+ load_config
68
+ perform
69
+ end
70
+
71
+ private
72
+
73
+ def load_config
74
+ input.parse!(ARGV)
75
+ end
76
+
77
+ def perform
78
+ config = CandyCheck::AppStore::Config.new(input.config)
79
+ puts 'Configuration'
80
+ pp config
81
+ puts
82
+ verifier = CandyCheck::AppStore::Verifier.new(config)
83
+ result = verifier.verify(input.receipt, input.secret)
84
+ puts
85
+ puts "#{result.class}:"
86
+ pp result
87
+ end
88
+ end
89
+
90
+ App.new
data/bin/cc_playstore ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'optparse'
5
+ require 'ostruct'
6
+ require 'pp'
7
+ require 'candy_check'
8
+
9
+ class InputParser
10
+ attr_reader :config, :package, :product_id, :token
11
+
12
+ def initialize
13
+ @config = {
14
+ application_name: 'CandyCheck',
15
+ application_version: version,
16
+ key_secret: 'notasecret'
17
+ }
18
+ configure!
19
+ end
20
+
21
+ def parse!(args)
22
+ @parser.parse!(args)
23
+ help! unless @package && @product_id && @token
24
+ end
25
+
26
+ private
27
+
28
+ def configure!
29
+ @parser = OptionParser.new do |opts|
30
+ @opts = opts
31
+ opts.banner = 'Usage cc_playstore [options]'
32
+ opts.separator ''
33
+ opts.separator 'Required options:'
34
+
35
+ opts.on('-e', '--issuer ISSUER', 'your service account\'s email') do |v|
36
+ config[:issuer] = v
37
+ end
38
+ opts.on('-k', '--key-file KEY_FILE', 'the key file to use') do |v|
39
+ config[:key_file] = v
40
+ end
41
+ opts.on('-p', '--package NAME', 'your app\'s package name') do |v|
42
+ @package = v
43
+ end
44
+ opts.on('-i', '--product-id ID', 'your product\'s id') do |v|
45
+ @product_id = v
46
+ end
47
+ opts.on('-t', '--token TOKEN', 'your the purchase token') do |v|
48
+ @token = v
49
+ end
50
+ opts.separator ''
51
+ opts.separator 'Optional options:'
52
+ opts.on('-s', '--secret SECRET', 'the secret to decrypt the key file, defaults to \'notasecret\'') do |v|
53
+ config[:key_secret] = v
54
+ end
55
+ opts.on('-a', '--app-name NAME', 'your application\'s name, default to \'CandyCheck\'') do |v|
56
+ config[:application_name] = v
57
+ end
58
+ opts.on('-r', '--app-version VERSION', "your application's version, default to '#{version}'") do |v|
59
+ config[:application_version] = v
60
+ end
61
+ opts.separator ''
62
+ opts.separator 'Further functions:'
63
+ opts.on_tail('-v', '--version', 'Show version') do
64
+ puts version
65
+ exit
66
+ end
67
+ opts.on_tail('-h', '--help', 'Show help') do
68
+ help!
69
+ end
70
+ end
71
+ end
72
+
73
+ def help!
74
+ puts @opts
75
+ exit
76
+ end
77
+
78
+ def version
79
+ CandyCheck::VERSION
80
+ end
81
+ end
82
+
83
+ class App
84
+ attr_reader :input
85
+
86
+ def initialize
87
+ @input = InputParser.new
88
+ load_config
89
+ perform
90
+ end
91
+
92
+ private
93
+
94
+ def load_config
95
+ input.parse!(ARGV)
96
+ end
97
+
98
+ def perform
99
+ puts "Package: #{input.package}"
100
+ puts "Product: #{input.product_id}"
101
+ puts "Token: #{input.token}"
102
+ puts
103
+ config = CandyCheck::PlayStore::Config.new(input.config)
104
+ puts 'Configuration'
105
+ pp config
106
+ puts
107
+ verifier = CandyCheck::PlayStore::Verifier.new(config)
108
+ puts 'Booting'
109
+ verifier.boot!
110
+ puts '-> done'
111
+ puts
112
+ result = verifier.verify(input.package, input.product_id, input.token)
113
+ puts
114
+ puts "#{result.class}:"
115
+ pp result
116
+ end
117
+ end
118
+
119
+ App.new