rack-in-app-purchase 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-in-app-purchase (0.0.1)
5
+ rack (~> 1.4)
6
+ sequel (~> 3.37.0)
7
+ sinatra (~> 1.3.2)
8
+ venice
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ commander (4.1.3)
14
+ highline (~> 1.6.11)
15
+ excon (0.17.0)
16
+ highline (1.6.15)
17
+ json (1.7.7)
18
+ rack (1.5.2)
19
+ rack-protection (1.3.2)
20
+ rack
21
+ rake (0.9.6)
22
+ rspec (0.6.4)
23
+ sequel (3.37.0)
24
+ sinatra (1.3.4)
25
+ rack (~> 1.4)
26
+ rack-protection (~> 1.3)
27
+ tilt (~> 1.3, >= 1.3.3)
28
+ terminal-table (1.4.5)
29
+ tilt (1.3.3)
30
+ venice (0.0.1)
31
+ commander (~> 4.1.2)
32
+ excon (~> 0.17.0)
33
+ json (~> 1.7.3)
34
+ terminal-table (~> 1.4.5)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ rack-in-app-purchase!
41
+ rake (~> 0.9.2)
42
+ rspec (~> 0.6.1)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Mattt Thompson (http://mattt.me/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Rack::InAppPurchase
2
+
3
+ Rack::InAppPurchase is Rack middleware that manages products for in-app-purchases and verifies receipts. Verifying receipts on the server ensures guards against unauthorized use of paid content, as well as providing real-time metrics on purchase history and usage.
4
+
5
+ Rack::InAppPurchase provides the following endpoints:
6
+
7
+ - `GET /products/identifiers`: Returns a JSON array of product identifiers from the database
8
+ - `POST /receipts/verify`: Verifies a receipt using Apple's receipt verification webservice, registers the receipt in the database, and returns the receipt.
9
+
10
+ ## Installation
11
+
12
+ $ gem install rack-in-app-purchase
13
+
14
+ ## Requirements
15
+
16
+ - Ruby 1.9
17
+ - PostgreSQL 9.1 running locally ([Postgres.app](http://postgresapp.com) is the easiest way to get a Postgres server running on your Mac)
18
+
19
+ ## Example Usage
20
+
21
+ Rack::InAppPurchase can be run as Rack middleware or as a single web application. All that is required is a connection to a Postgres database.
22
+
23
+ ### config.ru
24
+
25
+ ```ruby
26
+ require 'bundler'
27
+ Bundler.require
28
+
29
+ run Rack::InAppPurchase
30
+ ```
31
+
32
+ ### Objective-C
33
+
34
+ ```objective-c
35
+ NSURL *url = [NSURL URLWithString:@"https://your-webservice-url.com/verifyReceipt"];
36
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
37
+ [request setHTTPMethod:@"POST"];
38
+
39
+ NSString *params = [NSString stringWithFormat:@"receipt_data=%@", [receiptData base64EncodedString]];
40
+ NSData *httpBody = [params dataUsingEncoding:NSUTF8StringEncoding];
41
+ [request setHTTPBody:httpBody];
42
+
43
+ [NSURLConnection sendAsynchronousRequest:request
44
+ queue:[NSOperationQueue mainQueue]
45
+ completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
46
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
47
+ if (httpResponse.statusCode == 200) {
48
+ id receipt = [NSJSONSerialization JSONObjectWithData:data
49
+ options:0
50
+ error:nil];
51
+ NSLog(@"Received receipt: %@", receipt);
52
+ } else {
53
+ NSLog(@"Body: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
54
+ NSLog(@"ERROR: %@", error);
55
+ }
56
+ }];
57
+ ```
58
+
59
+ ### `curl`
60
+
61
+ $ curl -X POST -i -d "receipt-data=(Base64-Encoded String)" https://your-webservice-url.com/verifyReceipt
62
+
63
+ HTTP/1.1 203 Non-Authoritative Information
64
+ Content-Type: application/json;charset=utf-8
65
+ Content-Length: 365
66
+ Connection: keep-alive
67
+ Server: thin 1.5.0 codename Knife
68
+
69
+ {"status":0,"receipt":{"quantity":1,"product_id":"com.example.download","transaction_id":"1000000000000001","purchase_date":"Mon, 01 Jan 2013 12:00:00 GMT","original_transaction_id":"1000000000000001","original_purchase_date":"Mon, 01 Jan 2013 12:00:00 GMT","app_item_id":null,"version_external_identifier":null,"bid":"com.example.app","bvrs":"123456789"}}
70
+
71
+ An example application can be found in the `/example` directory of this repository.
72
+
73
+
74
+ ---
75
+
76
+ ## Contact
77
+
78
+ Mattt Thompson
79
+
80
+ - http://github.com/mattt
81
+ - http://twitter.com/mattt
82
+ - m@mattt.me
83
+
84
+ ## License
85
+
86
+ Rack::InAppPurchase is available under the MIT license. See the LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("rack-in-app-purchase.gemspec"))
5
+
6
+ task :build => "rack-in-app-purchase.gem"
7
+
8
+ file "rack-in-app-purchase.gem" => gemspec.files + ["rack-in-app-purchase.gemspec"] do
9
+ system "gem build rack-in-app-purchase.gemspec"
10
+ end
@@ -0,0 +1,60 @@
1
+ require 'rack'
2
+ require 'rack/contrib'
3
+
4
+ require 'sinatra/base'
5
+ require 'sinatra/param'
6
+ require 'rack'
7
+
8
+ require 'sequel'
9
+
10
+ require 'venice'
11
+
12
+ module Rack
13
+ class InAppPurchase < Sinatra::Base
14
+ VERSION = '0.0.1'
15
+
16
+ use Rack::PostBodyContentTypeParser
17
+ helpers Sinatra::Param
18
+
19
+ Sequel.extension :core_extensions, :migration
20
+
21
+ autoload :Product, ::File.join(::File.dirname(__FILE__), 'in-app-purchase/models/product')
22
+ autoload :Receipt, ::File.join(::File.dirname(__FILE__), 'in-app-purchase/models/receipt')
23
+
24
+ disable :raise_errors, :show_exceptions
25
+
26
+ before do
27
+ content_type :json
28
+ end
29
+
30
+ get '/products/identifiers' do
31
+ Product.where(is_enabled: true).map(:product_identifier).to_json
32
+ end
33
+
34
+ post '/receipts/verify' do
35
+ param :'receipt-data', String, required: true
36
+
37
+ status 203
38
+
39
+ begin
40
+ receipt = Venice::Receipt.verify!(params[:'receipt-data'])
41
+
42
+ Receipt.create({ip_address: request.ip}.merge(receipt.to_h))
43
+
44
+ content = settings.content_callback.call(receipt) rescue nil
45
+
46
+ {
47
+ status: 0,
48
+ receipt: receipt.to_h,
49
+ content: content
50
+ }.select{|k,v| v}.to_json
51
+ rescue Venice::ReceiptVerificationError => error
52
+ {
53
+ status: Integer(error.message)
54
+ }.to_json
55
+ rescue
56
+ halt 500
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table :in_app_purchase_products do
4
+ primary_key :id
5
+
6
+ column :product_identifier, :varchar, null: false
7
+ column :type, :varchar, null: false
8
+ column :title, :varchar, null: false
9
+ column :description, :varchar
10
+ column :price, :float8
11
+ column :price_locale, :varchar
12
+ column :is_enabled, :boolean, default: true
13
+
14
+ index :product_identifier
15
+ index :type
16
+ end
17
+
18
+ create_table :in_app_purchase_receipts do
19
+ primary_key :id
20
+
21
+ column :quantity, :int4
22
+ column :product_id, :varchar, null: false
23
+ column :transaction_id, :varchar, null: false
24
+ column :purchase_date, :timestamp, null: false
25
+ column :original_transaction_id, :varchar
26
+ column :original_purchase_date, :timestamp
27
+ column :app_item_id, :varchar
28
+ column :version_external_identifier, :varchar
29
+ column :bid, :varchar
30
+ column :bvrs, :varchar
31
+ column :ip_address, :inet
32
+ column :created_at, :timestamp
33
+
34
+ index :product_id
35
+ index :transaction_id
36
+ index :app_item_id
37
+ end
38
+ end
39
+
40
+ down do
41
+ drop_table :in_app_purchase_products
42
+ drop_table :in_app_purchase_receipts
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ module Rack
2
+ class InAppPurchase
3
+ class Product < Sequel::Model
4
+ TYPES = ["Consumable", "Non-Consumable", "Free Subscription", "Auto-Renewable Subscription", "Non-Renewable Subscription"]
5
+
6
+ plugin :json_serializer, naked: true, except: :id
7
+ plugin :validation_helpers
8
+ plugin :timestamps, force: true, update_on_create: true
9
+
10
+ self.dataset = :in_app_purchase_products
11
+ self.strict_param_setting = false
12
+ self.raise_on_save_failure = false
13
+
14
+ def validate
15
+ super
16
+
17
+ validates_presence [:product_identifier, :title, :description, :price, :price_locale]
18
+ validates_numeric :price
19
+ validates_includes TYPES, :type
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Rack
2
+ class InAppPurchase
3
+ DB = Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost:5432/in_app_purchase_example")
4
+ Sequel::Migrator.run(DB, ::File.join(::File.dirname(__FILE__), "../migrations"))
5
+
6
+ class Receipt < Sequel::Model
7
+ plugin :json_serializer, naked: true, except: :id
8
+ plugin :validation_helpers
9
+ plugin :timestamps, force: true
10
+
11
+ self.dataset = :in_app_purchase_receipts
12
+ self.strict_param_setting = false
13
+ self.raise_on_save_failure = false
14
+
15
+ def validate
16
+ super
17
+
18
+ validates_presence [:product_id, :transaction_id, :purchase_date]
19
+ validates_unique :transaction_id
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/in-app-purchase"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-in-app-purchase"
7
+ s.authors = ["Mattt Thompson"]
8
+ s.email = "m@mattt.me"
9
+ s.homepage = "http://mattt.me"
10
+ s.version = Rack::InAppPurchase::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = "Rack::InAppPurchase"
13
+ s.description = "Rack middleware for in-app purchase receipt verification and product listing."
14
+
15
+ s.add_development_dependency "rspec", "~> 0.6.1"
16
+ s.add_development_dependency "rake", "~> 0.9.2"
17
+
18
+ s.add_dependency "rack", "~> 1.4"
19
+ s.add_dependency "sinatra", "~> 1.3.2"
20
+ s.add_dependency "sequel", "~> 3.37.0"
21
+ s.add_dependency "venice"
22
+
23
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/ }
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-in-app-purchase
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mattt Thompson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70273146777820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.1
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70273146777820
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70273146777160 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70273146777160
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ requirement: &70273146776540 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '1.4'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70273146776540
47
+ - !ruby/object:Gem::Dependency
48
+ name: sinatra
49
+ requirement: &70273146775860 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.2
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70273146775860
58
+ - !ruby/object:Gem::Dependency
59
+ name: sequel
60
+ requirement: &70273146775280 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 3.37.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70273146775280
69
+ - !ruby/object:Gem::Dependency
70
+ name: venice
71
+ requirement: &70273146774760 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70273146774760
80
+ description: Rack middleware for in-app purchase receipt verification and product
81
+ listing.
82
+ email: m@mattt.me
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - ./Gemfile
88
+ - ./Gemfile.lock
89
+ - ./lib/rack/in-app-purchase/migrations/001_base_schema.rb
90
+ - ./lib/rack/in-app-purchase/models/product.rb
91
+ - ./lib/rack/in-app-purchase/models/receipt.rb
92
+ - ./lib/rack/in-app-purchase.rb
93
+ - ./LICENSE
94
+ - ./rack-in-app-purchase.gemspec
95
+ - ./Rakefile
96
+ - ./README.md
97
+ homepage: http://mattt.me
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ segments:
110
+ - 0
111
+ hash: -3903980305270513918
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: -3903980305270513918
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.15
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Rack::InAppPurchase
127
+ test_files: []