midpay 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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +1 -0
- data/lib/midpay.rb +60 -0
- data/lib/midpay/builder.rb +14 -0
- data/lib/midpay/hash/indifferent_access.rb +84 -0
- data/lib/midpay/hash/key_conversion.rb +92 -0
- data/lib/midpay/hash/merge_initializer.rb +12 -0
- data/lib/midpay/hash_extensions.rb +25 -0
- data/lib/midpay/signable_hash.rb +42 -0
- data/lib/midpay/strategy.rb +151 -0
- data/lib/midpay/version.rb +3 -0
- data/midpay.gemspec +26 -0
- data/spec/midpay/signable_hash_spec.rb +31 -0
- data/spec/midpay/strategy_spec.rb +99 -0
- data/spec/spec_helper.rb +37 -0
- metadata +133 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rvm use 1.9.3@midpay --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 xixilive
|
|
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,78 @@
|
|
|
1
|
+
# Midpay
|
|
2
|
+
|
|
3
|
+
A Rack Middleware for E-Commerce Payment Base-Strategy
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'midpay'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install midpay
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
For example, we have a strategy named Foo as following:
|
|
22
|
+
```ruby
|
|
23
|
+
class Foo
|
|
24
|
+
include ::Midpay::Strategy
|
|
25
|
+
|
|
26
|
+
def request_phase response
|
|
27
|
+
response.write("You are being redirected to a payment gateway......")
|
|
28
|
+
response.redirect a_url_for_payment_gateway_with_some_parameters
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def callback_phase payment_info
|
|
32
|
+
payment_info.extra = EXTRA_INFO #... whatever
|
|
33
|
+
payment_info.raw_data = RAW_DATA #... whatever
|
|
34
|
+
payment_info.success = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#register your strategy
|
|
39
|
+
::Midpay[:foo] = ::Foo
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In your rack app:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
use ::Midpay[:foo], YOUR_APPID, YOUR_KEY, :request_params_proc => {|params|
|
|
46
|
+
obj = BarModel.find(params[:bar_id])
|
|
47
|
+
{
|
|
48
|
+
:key1 => obj.value1,
|
|
49
|
+
:key2 => obj.value2
|
|
50
|
+
# all of the params to be sent to your payment gateway on request phase
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Send a request to Payment Gateway:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
get '/midpay/foo?bar_id=123'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Handle gateway callback request in your controller:
|
|
62
|
+
```ruby
|
|
63
|
+
def callback
|
|
64
|
+
current_strategy = request.env['midpay.strategy']
|
|
65
|
+
callback_data = request.env['midpay.callback']
|
|
66
|
+
# handle the callback logic
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For more details at WIKI
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
1. Fork it
|
|
75
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
76
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
77
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
78
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/midpay.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
lib = File.expand_path('../../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "midpay/version"
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'singleton'
|
|
7
|
+
require 'rack'
|
|
8
|
+
require 'uri'
|
|
9
|
+
require 'hashie'
|
|
10
|
+
|
|
11
|
+
module Midpay
|
|
12
|
+
class << self
|
|
13
|
+
def strategies
|
|
14
|
+
@@strategies ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def []= name, value
|
|
18
|
+
strategies[name.to_sym] = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def [] name
|
|
22
|
+
strategies[name.to_sym]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Configuration
|
|
27
|
+
include Singleton
|
|
28
|
+
|
|
29
|
+
attr_accessor :root_path, :logger
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@root_path = "/midpay"
|
|
33
|
+
@logger = ::Logger.new(STDOUT)
|
|
34
|
+
logger.progname = "midpay"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.config
|
|
39
|
+
Configuration.instance
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.logger
|
|
43
|
+
config.logger
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.configure
|
|
47
|
+
yield config
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
autoload :Builder, 'midpay/builder.rb'
|
|
51
|
+
autoload :Strategy, "midpay/strategy.rb"
|
|
52
|
+
autoload :HashExtensions, "midpay/hash_extensions.rb"
|
|
53
|
+
autoload :SignableHash, "midpay/signable_hash.rb"
|
|
54
|
+
|
|
55
|
+
module Errors
|
|
56
|
+
class InvalidPaymentInfo < ::StandardError; end
|
|
57
|
+
class InvalidSignature < ::StandardError; end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
class Builder < ::Rack::Builder
|
|
3
|
+
|
|
4
|
+
def midpay klass, *args, &block
|
|
5
|
+
raise "Invalid Middleware" unless (middleware = klass.is_a?(Class) ? klass : Midpay[klass])
|
|
6
|
+
use middleware, *args, &block
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
to_app.call(env)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
module HashExtensions
|
|
3
|
+
module IndifferentAccess
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
alias_method :regular_writer, :[]=
|
|
7
|
+
alias_method :[]=, :indifferent_writer
|
|
8
|
+
%w(default update fetch delete key? values_at).each do |m|
|
|
9
|
+
alias_method "regular_#{m}", m
|
|
10
|
+
alias_method m, "indifferent_#{m}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
%w(include? member? has_key?).each do |key_alias|
|
|
14
|
+
alias_method key_alias, :indifferent_key?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.inject!(hash)
|
|
20
|
+
(class << hash; self; end).send :include, IndifferentAccess
|
|
21
|
+
hash.convert!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.inject(hash)
|
|
25
|
+
inject!(hash.dup)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def convert_key(key)
|
|
29
|
+
key.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def convert!
|
|
33
|
+
keys.each do |k|
|
|
34
|
+
regular_writer convert_key(k), convert_value(self.regular_delete(k))
|
|
35
|
+
end
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def convert_value(value)
|
|
40
|
+
if hash_lacking_indifference?(value)
|
|
41
|
+
IndifferentAccess.inject(value.dup)
|
|
42
|
+
elsif value.is_a?(::Array)
|
|
43
|
+
value.dup.replace(value.map { |e| convert_value(e) })
|
|
44
|
+
else
|
|
45
|
+
value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def indifferent_default(key = nil)
|
|
50
|
+
return self[convert_key(key)] if key?(key)
|
|
51
|
+
regular_default(key)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def indifferent_update(other_hash)
|
|
55
|
+
return regular_update(other_hash) if hash_with_indifference?(other_hash)
|
|
56
|
+
other_hash.each_pair do |k,v|
|
|
57
|
+
self[k] = v
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def indifferent_writer(key, value); regular_writer convert_key(key), convert_value(value) end
|
|
62
|
+
def indifferent_fetch(key, *args); regular_fetch convert_key(key), *args end
|
|
63
|
+
def indifferent_delete(key); regular_delete convert_key(key) end
|
|
64
|
+
def indifferent_key?(key); regular_key? convert_key(key) end
|
|
65
|
+
def indifferent_values_at(*indices); indices.map{|i| self[i] } end
|
|
66
|
+
|
|
67
|
+
def indifferent_access?; true end
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
|
|
71
|
+
def hash_lacking_indifference?(other)
|
|
72
|
+
other.is_a?(::Hash) &&
|
|
73
|
+
!(other.respond_to?(:indifferent_access?) &&
|
|
74
|
+
other.indifferent_access?)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def hash_with_indifference?(other)
|
|
78
|
+
other.is_a?(::Hash) &&
|
|
79
|
+
other.respond_to?(:indifferent_access?) &&
|
|
80
|
+
other.indifferent_access?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
module HashExtensions
|
|
3
|
+
module StringifyKeys
|
|
4
|
+
# Convert all keys in the hash to strings.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# test = {:abc => 'def'}
|
|
8
|
+
# test.stringify_keys!
|
|
9
|
+
# test # => {'abc' => 'def'}
|
|
10
|
+
def stringify_keys!
|
|
11
|
+
keys.each do |k|
|
|
12
|
+
stringify_keys_recursively!(self[k])
|
|
13
|
+
self[k.to_s] = self.delete(k)
|
|
14
|
+
end
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Return a new hash with all keys converted
|
|
19
|
+
# to strings.
|
|
20
|
+
def stringify_keys
|
|
21
|
+
dup.stringify_keys!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# Stringify all keys recursively within nested
|
|
27
|
+
# hashes and arrays.
|
|
28
|
+
def stringify_keys_recursively!(object)
|
|
29
|
+
if self.class === object
|
|
30
|
+
object.stringify_keys!
|
|
31
|
+
elsif ::Array === object
|
|
32
|
+
object.each do |i|
|
|
33
|
+
stringify_keys_recursively!(i)
|
|
34
|
+
end
|
|
35
|
+
object
|
|
36
|
+
elsif object.respond_to?(:stringify_keys!)
|
|
37
|
+
object.stringify_keys!
|
|
38
|
+
else
|
|
39
|
+
object
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module SymbolizeKeys
|
|
45
|
+
# Convert all keys in the hash to symbols.
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# test = {'abc' => 'def'}
|
|
49
|
+
# test.symbolize_keys!
|
|
50
|
+
# test # => {:abc => 'def'}
|
|
51
|
+
def symbolize_keys!
|
|
52
|
+
keys.each do |k|
|
|
53
|
+
symbolize_keys_recursively!(self[k])
|
|
54
|
+
self[k.to_sym] = self.delete(k)
|
|
55
|
+
end
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return a new hash with all keys converted
|
|
60
|
+
# to symbols.
|
|
61
|
+
def symbolize_keys
|
|
62
|
+
dup.symbolize_keys!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
protected
|
|
66
|
+
|
|
67
|
+
# Symbolize all keys recursively within nested
|
|
68
|
+
# hashes and arrays.
|
|
69
|
+
def symbolize_keys_recursively!(object)
|
|
70
|
+
if self.class === object
|
|
71
|
+
object.symbolize_keys!
|
|
72
|
+
elsif ::Array === object
|
|
73
|
+
object.each do |i|
|
|
74
|
+
symbolize_keys_recursively!(i)
|
|
75
|
+
end
|
|
76
|
+
object
|
|
77
|
+
elsif object.respond_to?(:symbolize_keys!)
|
|
78
|
+
object.symbolize_keys!
|
|
79
|
+
else
|
|
80
|
+
object
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
module KeyConversion
|
|
86
|
+
def self.included(base)
|
|
87
|
+
base.send :include, SymbolizeKeys
|
|
88
|
+
base.send :include, StringifyKeys
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
module HashExtensions
|
|
3
|
+
require 'hashie/version'
|
|
4
|
+
|
|
5
|
+
def self.included base
|
|
6
|
+
if ::Hashie::VERSION.to_i >= 2
|
|
7
|
+
base.send :include, ::Hashie::Extensions::MergeInitializer
|
|
8
|
+
base.send :include, ::Hashie::Extensions::StringifyKeys
|
|
9
|
+
base.send :include, ::Hashie::Extensions::SymbolizeKeys
|
|
10
|
+
base.send :include, ::Hashie::Extensions::IndifferentAccess
|
|
11
|
+
else
|
|
12
|
+
|
|
13
|
+
require 'midpay/hash/merge_initializer'
|
|
14
|
+
require 'midpay/hash/indifferent_access'
|
|
15
|
+
require 'midpay/hash/key_conversion'
|
|
16
|
+
|
|
17
|
+
base.send :include, ::Midpay::HashExtensions::MergeInitializer
|
|
18
|
+
base.send :include, ::Midpay::HashExtensions::IndifferentAccess
|
|
19
|
+
base.send :include, ::Midpay::HashExtensions::StringifyKeys
|
|
20
|
+
base.send :include, ::Midpay::HashExtensions::SymbolizeKeys
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
class SignableHash < ::Hash
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
5
|
+
include ::Midpay::HashExtensions
|
|
6
|
+
|
|
7
|
+
def sign algorithm, &block
|
|
8
|
+
data = yield(self.dup)
|
|
9
|
+
begin
|
|
10
|
+
algorithm_const(algorithm).send(:hexdigest, data)
|
|
11
|
+
rescue
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def sign! key, algorithm, &block
|
|
17
|
+
self[key] = self.sign(algorithm, &block)
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_query
|
|
22
|
+
collect{|k,v| "#{::URI.encode_www_form_component(k.to_s)}=#{::URI.encode_www_form_component(v.to_s)}" }.sort.join("&")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def merge_if! hash
|
|
26
|
+
hash.each do |i|
|
|
27
|
+
self[i[0]] = i[1] unless self.key?(i[0])
|
|
28
|
+
end if hash.respond_to?(:each)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def algorithm_const algorithm
|
|
33
|
+
algorithm = self[algorithm] if key?(algorithm) && self[algorithm]
|
|
34
|
+
begin
|
|
35
|
+
::Digest.const_get(algorithm.to_s.upcase)
|
|
36
|
+
rescue
|
|
37
|
+
#
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module Midpay
|
|
2
|
+
module Strategy
|
|
3
|
+
|
|
4
|
+
class Options < ::Hashie::Mash; end
|
|
5
|
+
|
|
6
|
+
class PaymentInfo
|
|
7
|
+
require 'json'
|
|
8
|
+
attr_accessor :pay, :raw_data, :extra, :success
|
|
9
|
+
def initialize pay, &block
|
|
10
|
+
@pay = pay
|
|
11
|
+
@raw_data = {}
|
|
12
|
+
@extra = {}
|
|
13
|
+
@success = nil
|
|
14
|
+
yield(self) if block_given?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def valid?
|
|
18
|
+
!pay.to_s.empty? && raw_data.is_a?(::Hash) && !raw_data.empty?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_hash
|
|
22
|
+
{
|
|
23
|
+
:pay => pay,
|
|
24
|
+
:raw_data => raw_data,
|
|
25
|
+
:extra => extra,
|
|
26
|
+
:success => success?
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_json
|
|
31
|
+
to_hash.to_json
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def success?
|
|
35
|
+
!!success
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.included base
|
|
40
|
+
base.extend ClassMethods
|
|
41
|
+
Midpay[base.strategy_name.to_sym] = base
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module ClassMethods
|
|
45
|
+
def default_options
|
|
46
|
+
@default_options ||= Options.new(name: self.strategy_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default_arguments
|
|
50
|
+
@default_arguments ||= Options.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def option name, value = nil
|
|
54
|
+
default_options[name] = value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set name, value = nil
|
|
58
|
+
default_arguments[name] = value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def strategy_name
|
|
62
|
+
self.name.split("::").last.to_s.gsub(/(?!(^))([A-Z])/,'_\1\2').downcase
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attr_reader :app, :env, :options, :arguments
|
|
67
|
+
|
|
68
|
+
def initialize app, *args, &block
|
|
69
|
+
@app, @env = app, nil
|
|
70
|
+
opts = args.last.is_a?(::Hash) ? args.pop : {}
|
|
71
|
+
@options = self.class.default_options.dup
|
|
72
|
+
[:app_key, :app_secret, :request_params_proc].each do |k|
|
|
73
|
+
options[k] = opts.delete(k)
|
|
74
|
+
end
|
|
75
|
+
@arguments = self.class.default_arguments.dup.merge(opts)
|
|
76
|
+
options.request_params_proc ||= block
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def call(env)
|
|
80
|
+
dup._call(env)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def _call(env)
|
|
84
|
+
@env = env;
|
|
85
|
+
on_path?(callback_path) ? callback_call! : (on_path?(request_path) ? request_call! : app.call(env))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def root_path
|
|
89
|
+
::Midpay.config.root_path.to_s.sub(/\/$/,'')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def request_path
|
|
93
|
+
path = options.request_path || "#{root_path}/#{options.name}"
|
|
94
|
+
path.sub(/\/$/,'')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def callback_path
|
|
98
|
+
path = options.callback_path || "#{root_path}/#{options.name}/callback"
|
|
99
|
+
path.sub(/\/$/,'')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def current_path
|
|
103
|
+
request.path_info.downcase.sub(/\/$/,'')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_path? path
|
|
107
|
+
current_path.casecmp(path) == 0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def callback_url
|
|
111
|
+
URI.join(request.url, callback_path).to_s
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def request_call!
|
|
115
|
+
log :info, "midpay request_call!"
|
|
116
|
+
response = ::Rack::Response.new
|
|
117
|
+
request_phase(response)
|
|
118
|
+
response.finish
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def request_data
|
|
122
|
+
proc = options.request_params_proc
|
|
123
|
+
@request_data ||= ::Midpay::SignableHash.new(proc.respond_to?(:call) ? proc.call(request.params.dup) : {})
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def callback_call!
|
|
127
|
+
log :info, "midpay callback_call!"
|
|
128
|
+
pi = PaymentInfo.new(options.name)
|
|
129
|
+
callback_phase(pi)
|
|
130
|
+
raise ::Midpay::Errors::InvalidPaymentInfo.new unless pi.valid?
|
|
131
|
+
env['midpay.strategy'] = self
|
|
132
|
+
env['midpay.callback'] = pi
|
|
133
|
+
app.call(env)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def request
|
|
137
|
+
@request ||= ::Rack::Request.new(@env)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def request_params
|
|
141
|
+
@request_params ||= ::Midpay::SignableHash.new(@request.params || {})
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def log(l ,msg)
|
|
145
|
+
::Midpay.logger.send(l, msg)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def request_phase(response); raise NotImplementedError.new; end
|
|
149
|
+
def callback_phase; raise NotImplementedError.new; end
|
|
150
|
+
end
|
|
151
|
+
end
|
data/midpay.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'midpay/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "midpay"
|
|
8
|
+
spec.version = Midpay::VERSION
|
|
9
|
+
spec.authors = ["xixilive"]
|
|
10
|
+
spec.email = ["xixilive@gmail.com"]
|
|
11
|
+
spec.description = %q{midpay}
|
|
12
|
+
spec.summary = %q{midpay}
|
|
13
|
+
spec.homepage = ""
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency 'hashie'
|
|
22
|
+
spec.add_dependency 'rack', ">= 1.4"
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
|
25
|
+
spec.add_development_dependency "rake"
|
|
26
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Midpay::SignableHash do
|
|
4
|
+
let(:signable){
|
|
5
|
+
Midpay::SignableHash.new({:foo => "Foo", :bar => "Bar", :url => "http://example.com"}, "default")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
context '#sign' do
|
|
9
|
+
it 'should return a signature-value that generated by algorithm' do
|
|
10
|
+
#foo=Foo&bar=Bar&url=http://example.comSECRET MD5=cc2f3b4894dd34fe101df3d06184c386
|
|
11
|
+
sign = signable.sign('MD5'){|hash| hash.to_a.collect{|i| i.join("=") }.join("&") + "SECRET" }
|
|
12
|
+
expect(sign).to eq('cc2f3b4894dd34fe101df3d06184c386')
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context '#sign!' do
|
|
17
|
+
it "should append a new specified key with signature-value to current hash" do
|
|
18
|
+
signable.sign!(:sign, 'MD5'){|hash| hash.to_a.collect{|i| i.join("=") }.join("&") + "SECRET" }
|
|
19
|
+
expect(signable[:sign]).to eq('cc2f3b4894dd34fe101df3d06184c386')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context '#to_query' do
|
|
24
|
+
it 'should return a URL formatted string in order' do
|
|
25
|
+
expect(signable.to_query).to eq("bar=Bar&foo=Foo&url=http%3A%2F%2Fexample.com")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context '#merge_if!' do
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
class TestStrategy
|
|
4
|
+
include Midpay::Strategy
|
|
5
|
+
|
|
6
|
+
option :name, "test"
|
|
7
|
+
|
|
8
|
+
def request_phase response
|
|
9
|
+
response.write("You are being redirected to /?return_url=#{request_data[:return_url]}&callback=#{callback_url}")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def callback_phase pi
|
|
13
|
+
pi.raw_data = request.params
|
|
14
|
+
pi.success = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe TestStrategy do
|
|
19
|
+
include Rack::Test::Methods
|
|
20
|
+
|
|
21
|
+
let(:request_params_proc){
|
|
22
|
+
Proc.new do |params|
|
|
23
|
+
{
|
|
24
|
+
:return_url =>'/return'
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let(:inner_app){
|
|
30
|
+
lambda {|env| [200, {'Content-Type' => 'text/html'}, ['body']] }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let(:app){
|
|
34
|
+
TestStrategy.new(inner_app, :app_key => "APPKEY", :app_secret => "APPSECRET", :request_params_proc => request_params_proc)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it "on request phase" do
|
|
38
|
+
get '/midpay/test'
|
|
39
|
+
expect(last_response.body).to eq("You are being redirected to /?return_url=/return&callback=http://example.org/midpay/test/callback")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "on callback_phase" do
|
|
43
|
+
get '/midpay/test/callback?success=1'
|
|
44
|
+
expect(last_request.env['midpay.callback'].pay).to eq("test")
|
|
45
|
+
expect(last_request.env['midpay.callback'].raw_data).to eq({"success" => "1"})
|
|
46
|
+
expect(last_request.env['midpay.callback'].success?).to be_true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'out of midpay phases' do
|
|
50
|
+
get '/'
|
|
51
|
+
expect(last_response.body).to eq("body")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe Midpay::Strategy::Options do
|
|
56
|
+
let(:options){
|
|
57
|
+
Midpay::Strategy::Options.new :foo=>'foo', :bar=>'bar'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it 'indifferent access' do
|
|
61
|
+
expect(options['bar']).to eq(options[:bar])
|
|
62
|
+
options[:another] = 'another'
|
|
63
|
+
expect(options['another']).to eq('another')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'method access' do
|
|
67
|
+
expect(options.bar).to eq(options[:bar])
|
|
68
|
+
options.another = 'another'
|
|
69
|
+
expect(options[:another]).to eq('another')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe Midpay::Strategy::PaymentInfo do
|
|
74
|
+
let(:pi){
|
|
75
|
+
Midpay::Strategy::PaymentInfo.new('test'){|pi|
|
|
76
|
+
pi.raw_data = {"foo"=>"bar"}
|
|
77
|
+
pi.extra = {"extra"=>"data"}
|
|
78
|
+
pi.success = true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
it 'should be valid given pay and raw_data are NOT blank' do
|
|
83
|
+
expect(pi.valid?).to be_true
|
|
84
|
+
expect(Midpay::Strategy::PaymentInfo.new('test').valid?).to be_false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it '#to_hash' do
|
|
88
|
+
expect(pi.to_hash).to eq({pay: 'test', raw_data: {"foo"=>"bar"}, extra: {"extra"=>"data"}, success: true})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it '#to_json' do
|
|
92
|
+
expect(pi.to_json).to eq('{"pay":"test","raw_data":{"foo":"bar"},"extra":{"extra":"data"},"success":true}')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it '#success?' do
|
|
96
|
+
expect(pi.success?).to be_true
|
|
97
|
+
expect(Midpay::Strategy::PaymentInfo.new('test').success?).to be_false
|
|
98
|
+
end
|
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding:utf-8
|
|
2
|
+
require 'rspec'
|
|
3
|
+
require 'rspec/autorun'
|
|
4
|
+
require 'midpay'
|
|
5
|
+
require 'rack/test'
|
|
6
|
+
|
|
7
|
+
if %W[on yes true 1].include?(ENV['RCOV'])
|
|
8
|
+
require 'simplecov'
|
|
9
|
+
require 'simplecov-rcov'
|
|
10
|
+
class SimpleCov::Formatter::MergedFormatter
|
|
11
|
+
def format(result)
|
|
12
|
+
SimpleCov::Formatter::HTMLFormatter.new.format(result)
|
|
13
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
|
17
|
+
|
|
18
|
+
SimpleCov.start "test_frameworks" do
|
|
19
|
+
#add_filter ''
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) }
|
|
24
|
+
|
|
25
|
+
RSpec.configure do |c|
|
|
26
|
+
c.before(:suite) do
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
c.before(:each) do
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
c.after(:suite) do
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: midpay
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- xixilive
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-08-30 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: hashie
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rack
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '1.4'
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '1.4'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: bundler
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ~>
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.3'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.3'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: rake
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
description: midpay
|
|
79
|
+
email:
|
|
80
|
+
- xixilive@gmail.com
|
|
81
|
+
executables: []
|
|
82
|
+
extensions: []
|
|
83
|
+
extra_rdoc_files: []
|
|
84
|
+
files:
|
|
85
|
+
- .gitignore
|
|
86
|
+
- .rspec
|
|
87
|
+
- .rvmrc
|
|
88
|
+
- Gemfile
|
|
89
|
+
- LICENSE.txt
|
|
90
|
+
- README.md
|
|
91
|
+
- Rakefile
|
|
92
|
+
- lib/midpay.rb
|
|
93
|
+
- lib/midpay/builder.rb
|
|
94
|
+
- lib/midpay/hash/indifferent_access.rb
|
|
95
|
+
- lib/midpay/hash/key_conversion.rb
|
|
96
|
+
- lib/midpay/hash/merge_initializer.rb
|
|
97
|
+
- lib/midpay/hash_extensions.rb
|
|
98
|
+
- lib/midpay/signable_hash.rb
|
|
99
|
+
- lib/midpay/strategy.rb
|
|
100
|
+
- lib/midpay/version.rb
|
|
101
|
+
- midpay.gemspec
|
|
102
|
+
- spec/midpay/signable_hash_spec.rb
|
|
103
|
+
- spec/midpay/strategy_spec.rb
|
|
104
|
+
- spec/spec_helper.rb
|
|
105
|
+
homepage: ''
|
|
106
|
+
licenses:
|
|
107
|
+
- MIT
|
|
108
|
+
post_install_message:
|
|
109
|
+
rdoc_options: []
|
|
110
|
+
require_paths:
|
|
111
|
+
- lib
|
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ! '>='
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
|
+
none: false
|
|
120
|
+
requirements:
|
|
121
|
+
- - ! '>='
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
requirements: []
|
|
125
|
+
rubyforge_project:
|
|
126
|
+
rubygems_version: 1.8.25
|
|
127
|
+
signing_key:
|
|
128
|
+
specification_version: 3
|
|
129
|
+
summary: midpay
|
|
130
|
+
test_files:
|
|
131
|
+
- spec/midpay/signable_hash_spec.rb
|
|
132
|
+
- spec/midpay/strategy_spec.rb
|
|
133
|
+
- spec/spec_helper.rb
|