candy_check 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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