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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +15 -0
- data/bin/cc_appstore +90 -0
- data/bin/cc_playstore +119 -0
- data/candy_check.gemspec +31 -0
- data/lib/candy_check/app_store/client.rb +57 -0
- data/lib/candy_check/app_store/config.rb +30 -0
- data/lib/candy_check/app_store/receipt.rb +83 -0
- data/lib/candy_check/app_store/verification.rb +49 -0
- data/lib/candy_check/app_store/verification_failure.rb +60 -0
- data/lib/candy_check/app_store/verifier.rb +69 -0
- data/lib/candy_check/app_store.rb +12 -0
- data/lib/candy_check/play_store/client.rb +102 -0
- data/lib/candy_check/play_store/config.rb +51 -0
- data/lib/candy_check/play_store/discovery_repository.rb +33 -0
- data/lib/candy_check/play_store/receipt.rb +81 -0
- data/lib/candy_check/play_store/verification.rb +46 -0
- data/lib/candy_check/play_store/verification_failure.rb +30 -0
- data/lib/candy_check/play_store/verifier.rb +52 -0
- data/lib/candy_check/play_store.rb +15 -0
- data/lib/candy_check/utils/attribute_reader.rb +30 -0
- data/lib/candy_check/utils/config.rb +40 -0
- data/lib/candy_check/utils.rb +2 -0
- data/lib/candy_check/version.rb +4 -0
- data/lib/candy_check.rb +8 -0
- data/spec/app_store/client_spec.rb +55 -0
- data/spec/app_store/config_spec.rb +41 -0
- data/spec/app_store/receipt_spec.rb +92 -0
- data/spec/app_store/verifcation_failure_spec.rb +28 -0
- data/spec/app_store/verification_spec.rb +66 -0
- data/spec/app_store/verifier_spec.rb +110 -0
- data/spec/candy_check_spec.rb +9 -0
- data/spec/fixtures/api_cache.dump +1 -0
- data/spec/fixtures/play_store/api_cache.dump +1 -0
- data/spec/fixtures/play_store/auth_failure.txt +18 -0
- data/spec/fixtures/play_store/auth_success.txt +20 -0
- data/spec/fixtures/play_store/discovery.txt +2841 -0
- data/spec/fixtures/play_store/dummy.p12 +0 -0
- data/spec/fixtures/play_store/empty.txt +17 -0
- data/spec/fixtures/play_store/products_failure.txt +29 -0
- data/spec/fixtures/play_store/products_success.txt +22 -0
- data/spec/play_store/client_spec.rb +104 -0
- data/spec/play_store/config_spec.rb +96 -0
- data/spec/play_store/discovery_respository_spec.rb +31 -0
- data/spec/play_store/receipt_spec.rb +88 -0
- data/spec/play_store/verification_failure_spec.rb +35 -0
- data/spec/play_store/verification_spec.rb +80 -0
- data/spec/play_store/verifier_spec.rb +95 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/with_fixtures.rb +9 -0
- data/spec/support/with_temp_file.rb +23 -0
- 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
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.5
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
+
[](http://badge.fury.io/rb/candy_check)
|
4
|
+
[](https://travis-ci.org/jnbt/candy_check)
|
5
|
+
[](https://coveralls.io/r/jnbt/candy_check?branch=master)
|
6
|
+
[](http://inch-ci.org/github/jnbt/candy_check)
|
7
|
+
[](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
|