candy_check 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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
|