magpie 0.8.6.2 → 0.8.8

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 (71) hide show
  1. data/README.md +86 -11
  2. data/Rakefile +7 -0
  3. data/bin/mag +3 -1
  4. data/lib/apps.rb +43 -0
  5. data/lib/magpie.rb +15 -25
  6. data/lib/magpie/goose.rb +88 -0
  7. data/lib/magpie/mouse.rb +43 -0
  8. data/lib/magpie/rubber.rb +16 -0
  9. data/lib/magpie/server.rb +18 -2
  10. data/lib/magpie/utils.rb +73 -22
  11. data/lib/middles/alipay.rb +3 -13
  12. data/lib/middles/chinabank.rb +3 -13
  13. data/lib/middles/snake.rb +116 -0
  14. data/lib/middles/tenpay.rb +22 -0
  15. data/lib/models/alipay.rb +96 -127
  16. data/lib/models/chinabank.rb +64 -99
  17. data/lib/models/dung.rb +66 -0
  18. data/lib/models/tenpay.rb +89 -0
  19. data/lib/views/fail.html.erb +12 -0
  20. data/lib/views/layouts/app.html.erb +42 -0
  21. data/lib/views/success.html.erb +34 -0
  22. data/magpie.gemspec +46 -4
  23. data/test/helper.rb +6 -1
  24. data/test/partner.yml +3 -0
  25. data/test/test.log +3152 -0
  26. data/test/test_alipay.rb +6 -15
  27. data/test/test_chinabank.rb +13 -5
  28. data/test/test_dung.rb +80 -0
  29. data/test/test_object.rb +18 -0
  30. data/test/test_snake.rb +55 -0
  31. data/test/test_tenpay.rb +144 -0
  32. data/test/test_utils.rb +35 -0
  33. metadata +43 -60
  34. data/doc/AlipayModel.html +0 -2010
  35. data/doc/ChinabankModel.html +0 -1245
  36. data/doc/Magpie.html +0 -255
  37. data/doc/Magpie/Alipay.html +0 -254
  38. data/doc/Magpie/Chinabank.html +0 -252
  39. data/doc/Magpie/Mothlog.html +0 -252
  40. data/doc/Magpie/Server.html +0 -241
  41. data/doc/Magpie/Server/Options.html +0 -245
  42. data/doc/Magpie/Utils.html +0 -85
  43. data/doc/_index.html +0 -186
  44. data/doc/class_list.html +0 -36
  45. data/doc/css/common.css +0 -1
  46. data/doc/css/full_list.css +0 -53
  47. data/doc/css/style.css +0 -307
  48. data/doc/file.COPYING.html +0 -78
  49. data/doc/file.README.html +0 -162
  50. data/doc/file_list.html +0 -38
  51. data/doc/frames.html +0 -13
  52. data/doc/index.html +0 -162
  53. data/doc/js/app.js +0 -202
  54. data/doc/js/full_list.js +0 -149
  55. data/doc/js/jquery.js +0 -154
  56. data/doc/method_list.html +0 -499
  57. data/doc/top-level-namespace.html +0 -90
  58. data/lib/doc/_index.html +0 -72
  59. data/lib/doc/class_list.html +0 -36
  60. data/lib/doc/css/common.css +0 -1
  61. data/lib/doc/css/full_list.css +0 -53
  62. data/lib/doc/css/style.css +0 -307
  63. data/lib/doc/file_list.html +0 -35
  64. data/lib/doc/frames.html +0 -13
  65. data/lib/doc/index.html +0 -72
  66. data/lib/doc/js/app.js +0 -202
  67. data/lib/doc/js/full_list.js +0 -149
  68. data/lib/doc/js/jquery.js +0 -154
  69. data/lib/doc/method_list.html +0 -35
  70. data/lib/doc/top-level-namespace.html +0 -78
  71. data/lib/middles/mothlog.rb +0 -33
@@ -1,16 +1,28 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require 'open-uri'
3
+ require 'hpricot'
4
+ require 'iconv'
2
5
  require 'net/https'
3
6
  require 'uri'
4
7
 
5
8
  module Magpie
6
9
  module Utils
7
10
 
8
- private
11
+ def dig(env)
12
+ status, header, body = @app.call env
13
+ req = Rack::Request.new env
14
+ doc = send_req_to @pay_gateway, req
15
+ red_text = (doc/@red_xpath).inner_text
16
+ red_text = (doc/@error_xpath).inner_text if red_text.blank? and @error_xpath
17
+ red_text = Iconv.iconv("UTF-8//IGNORE","GBK//IGNORE", red_text).to_s
18
+ return status, header, body, req, red_text
19
+ end
20
+
9
21
  def send_req_to(gw, req)
10
22
  text = case req.request_method
11
- when "GET"; get_query(gw, req.query_string)
12
- when "POST"; post_query(gw, req.params)
13
- end
23
+ when "GET"; get_query(gw, req.query_string).body
24
+ when "POST"; post_query(gw, req.params).body
25
+ end
14
26
  doc = Hpricot text
15
27
  end
16
28
 
@@ -23,31 +35,50 @@ module Magpie
23
35
 
24
36
  def hash_to_xml(h = { })
25
37
  h.inject(""){ |xml, (k, v)|
26
- xml << "<#{k}>"
27
- Hash === v ? xml << hash_to_xml(v) : xml << v.to_s
28
- xml << "</#{k}>"
38
+ case v
39
+ when Hash, String
40
+ xml << "<#{k}>"
41
+ xml << (Hash === v ? hash_to_xml(v) : v)
42
+ xml << "</#{k}>"
43
+ when Array
44
+ v.each{ |vv| xml << hash_to_xml(k => vv)}
45
+ xml
46
+ end
47
+
29
48
  }
30
49
  end
31
50
 
32
51
  def get_xml_body(env, am, red_text)
33
- if red_text =~ /错误|\d+/
34
- final_error = get_final_error red_text
35
- am.valid?
36
- xml_body = build_xml(:is_success => "F", :errors => am.errors.merge(:final => final_error))
37
- env["magpie.errors.info"] = am.errors.merge(:final => final_error)
52
+ if red_text.blank?
53
+ notify_res = get_notify_res(am)
54
+ xml_body = build_xml(:payment_success => "Yes", :business => notify_res )
38
55
  else
39
- begin_at = Time.now
40
- notify_res = am.send_notify
41
- now = Time.now
42
- env["magpie.notify"] = ["POST", am.notify_url, now.strftime("%d/%b/%Y %H:%M:%S"), now - begin_at, am.notify.inspect, notify_res ]
43
- xml_body = build_xml(:is_success => "T")
56
+ am.valid?
57
+ xml_body = build_xml(:payment_success => "No", :errors => am.errors.merge(:final => red_text))
58
+ log_errors(am.errors.merge(:final => red_text))
44
59
  end
45
60
  xml_body
46
61
  end
47
62
 
48
- # 在具体的中间件中重写
49
- def get_final_error(red_text)
50
- ""
63
+ def get_notify_res(am)
64
+ case am
65
+ when AlipayModel, ChinabankModel
66
+ notify_res = send_notify("POST", am.notify_url, am.notify).gsub(/<[^>]*>|<\/[^>]*> |\s/m, '')
67
+ method = "POST"
68
+ when TenpayModel
69
+ notify_res = send_notify("GET", am.notify_url, am.notify_string).gsub(/<[^>]*>|<\/[^>]*>|\s/m, '')
70
+ method = "GET"
71
+ end
72
+ log_notify(method, am.notify_url, am.notify, notify_res)
73
+ notify_res
74
+ end
75
+
76
+ def log_notify(method, notify_url, notify_params, notify_res)
77
+ Magpie.logger.info(FORMAT_NOTIFY % [method, notify_url, Time.now.strftime("%d/%b/%Y %H:%M:%S"), notify_params.inspect, notify_res])
78
+ end
79
+
80
+ def log_errors(errors = { })
81
+ Magpie.logger.info(errors.map{ |kv| "%s: %s" % kv }.join("\n"))
51
82
  end
52
83
 
53
84
  def start_http(url, req)
@@ -63,7 +94,6 @@ module Magpie
63
94
  url = URI.parse(url + "?" + q_string)
64
95
  req = Net::HTTP::Get.new("#{url.path}?#{url.query}")
65
96
  res = start_http(url, req)
66
- res.body
67
97
  end
68
98
 
69
99
  def post_query(url, params)
@@ -71,7 +101,28 @@ module Magpie
71
101
  req = Net::HTTP::Post.new(url.path)
72
102
  req.set_form_data params
73
103
  res = start_http(url, req)
74
- res.body
104
+ end
105
+
106
+ # 向商户系统发送通知
107
+ # @param [String, String, Hash] url是商户系统用来接收通知的url, notify是支付平台发过来的参数
108
+ # @return [String] 如果有异常需要你确认url是否有效
109
+ def send_notify(method, url, notify)
110
+ times = [4, 6, 2]
111
+ Rubber.try(3){|i|
112
+ timeout(times[3-i]) do
113
+ res = case method.to_s.upcase
114
+ when "GET"; get_query(url, notify)
115
+ when "POST"; post_query(url, notify)
116
+ end
117
+ case res
118
+ when Net::HTTPSuccess, Net::HTTPRedirection; res.body
119
+ else
120
+ raise "#{res.class}@#{res.code}"
121
+ end
122
+ end
123
+ }
124
+ rescue Exception => e
125
+ "发送通知时出现异常#{e}, 请确认#{url}在你的商户系统中可用, 比如#{url}是否可以#{method.upcase}方式接收其他应用的请求"
75
126
  end
76
127
 
77
128
  end
@@ -9,22 +9,12 @@ module Magpie
9
9
  def initialize(app, pay_gateway = "https://www.alipay.com/cooperate/gateway.do")
10
10
  @app = app
11
11
  @pay_gateway = pay_gateway
12
+ @red_xpath = "//div[@id='Info']/div[@class='ErrorInfo']/div[@class='Todo']"
12
13
  end
13
14
 
14
15
  def call(env)
15
- status, header, body = @app.call(env)
16
- req = Rack::Request.new(env)
17
- doc = send_req_to @pay_gateway, req
18
- red_text = (doc/"//div[@id='Info']/div[@class='ErrorInfo']/div[@class='Todo']").inner_text
19
- red_text = Iconv.iconv("UTF-8//IGNORE","GBK//IGNORE", red_text).to_s
20
- am = AlipayModel.new(req.params)
21
- [status, header, get_xml_body(env, am, red_text)]
22
- end
23
-
24
- private
25
-
26
- def get_final_error(red_text)
27
- red_text
16
+ status, header, body, req, red_text = dig env
17
+ [status, header, get_xml_body(env, AlipayModel.new(req.params), red_text)]
28
18
  end
29
19
 
30
20
  end
@@ -9,23 +9,13 @@ module Magpie
9
9
  def initialize(app, pay_gateway = "https://pay3.chinabank.com.cn/PayGate")
10
10
  @app = app
11
11
  @pay_gateway = pay_gateway
12
+ @red_xpath = "//strong[@class='red']"
12
13
  end
13
14
 
14
15
  def call(env)
15
- status, header, body = @app.call(env)
16
- req = Rack::Request.new(env)
17
- doc = send_req_to @pay_gateway, req
18
- red_text = Iconv.iconv("UTF-8//IGNORE","GBK//IGNORE", (doc/"//strong[@class='red']").inner_text).to_s
19
- am = ChinabankModel.new(req.params)
20
- [status, header, get_xml_body(env, am, red_text)]
16
+ status, header, body, req, red_text = dig env
17
+ [status, header, get_xml_body(env, ChinabankModel.new(req.params), red_text)]
21
18
  end
22
19
 
23
- private
24
-
25
- def get_final_error(red_text)
26
- red_text.match(/出错了!(.*)/)[1]
27
- end
28
-
29
-
30
20
  end
31
21
  end
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'erb'
3
+
4
+ module Magpie
5
+
6
+ class Snake
7
+ include Utils
8
+
9
+ def self.reg(snake, target, state)
10
+ proc{ instance_method("#{target}_#{state}").bind(snake).call}
11
+ end
12
+
13
+ def initialize(app, &block)
14
+ @app = app
15
+ @block = block
16
+ end
17
+
18
+ def call(env)
19
+ state, header, body = @app.call(env)
20
+ @block.call(self)
21
+ @req = Rack::Request.new(env)
22
+ @urls[@req.request_method].each { |path, lamb|
23
+ if @req.path_info =~ Regexp.new("^#{path}$")
24
+ body = lamb.call
25
+ break
26
+ end
27
+ }
28
+ [state, header, body]
29
+ rescue Exception => e
30
+ Magpie.logger.info(e.inspect + ":\n" + e.backtrace[0..8].join("\n"))
31
+ [500, header, "500, 请查看日志,了解异常原因"]
32
+ end
33
+
34
+ def tongue(target, contents = { })
35
+ @urls ||= { "GET" => { }, "POST" => { }}
36
+ states = [contents[:states]].flatten.compact
37
+ route("GET", target, states)
38
+ actions = [contents[:actions]].flatten.compact
39
+ route("POST", target, actions)
40
+ end
41
+
42
+ def reg(target, state)
43
+ self.class.reg(self, target, state)
44
+ end
45
+
46
+ def route(method, target, states)
47
+ routes = states.inject({ }){ |h, state|
48
+ url_path = "/#{target}/#{state}"
49
+ h[url_path] = reg(target, state)
50
+ h["/#{target}"] = reg(target, state) if state.to_s == "index"
51
+ h
52
+ }
53
+ @urls[method.to_s.upcase].merge!(routes)
54
+ end
55
+
56
+ def alipay_index
57
+ @am = AlipayModel.new(@req.params)
58
+ @title = "支付宝-收银台"
59
+ render_success_or_fail
60
+ end
61
+
62
+ def chinabank_index
63
+ @am = ChinabankModel.new(@req.params)
64
+ @title = "网银在线-收银台"
65
+ render_success_or_fail
66
+ end
67
+
68
+ def tenpay_index
69
+ @am = TenpayModel.new(@req.params)
70
+ @title = "财付通-收银台"
71
+ render_success_or_fail
72
+ end
73
+
74
+ def order_pay
75
+ return "支付失败, 缺少足够的参数" if @req.params.blank?
76
+ case @req.params["notify_kind"]
77
+ when "alipay", "chinabank"
78
+ notify_res = send_notify("POST", @req.params["notify_url"], query_to_hash(@req.params["notify"]))
79
+ method = "POST"
80
+ when "tenpay"
81
+ notify_res = send_notify("GET", @req.params["notify_url"], @req.params["notify"])
82
+ method = "GET"
83
+ end
84
+ log_notify(method, @req.params["notify_url"], query_to_hash(@req.params["notify"]), notify_res)
85
+ notify_res
86
+ end
87
+
88
+ private
89
+
90
+ def render(file_name, options = { })
91
+ layout = options["layout"] || "layouts/app.html.erb"
92
+ file_path = File.join(File.dirname(__FILE__), "../..", "lib", "views", "#{file_name}.html.erb")
93
+ layout_path = File.join(File.dirname(__FILE__), "../..", "lib", "views", layout)
94
+ template = ERB.new(File.read(file_path))
95
+ layout = ERB.new(File.read(layout_path))
96
+ @yield = template.result(binding)
97
+ layout.result(binding)
98
+ end
99
+
100
+ def query_to_hash(query)
101
+ hash_params = query.split("&").inject({ }){ |h, q| qs = q.split("="); h[qs[0]] = qs[1]; h }
102
+ end
103
+
104
+ def render_success_or_fail
105
+ if @am.valid?
106
+ @dung = Dung.new(@am)
107
+ render("success")
108
+ else
109
+ log_errors(@am.errors)
110
+ render("fail")
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,22 @@
1
+
2
+ require 'models/tenpay'
3
+
4
+ module Magpie
5
+
6
+ class Tenpay
7
+ include Utils
8
+
9
+ def initialize(app, pay_gateway = "http://service.tenpay.com/cgi-bin/v3.0/payservice.cgi")
10
+ @app = app
11
+ @pay_gateway = pay_gateway
12
+ @red_xpath = "//div[@id='error-info']"
13
+ @error_xpath = "//td[@class='font_14']"
14
+ end
15
+
16
+ def call(env)
17
+ status, header, body, req, red_text = dig env
18
+ [status, header, get_xml_body(env, TenpayModel.new(req.params), red_text)]
19
+ end
20
+
21
+ end
22
+ end
@@ -1,151 +1,92 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- class AlipayModel
4
- include ActiveModel::Validations
5
- attr_accessor :service,
6
- :partner,
7
- :notify_url,
8
- :return_url,
9
- :sign,
10
- :sign_type,
11
- :subject,
12
- :out_trade_no,
13
- :payment_type,
14
- :show_url,
15
- :body,
16
- :price,
17
- :total_fee,
18
- :quantity,
19
- :seller_email,
20
- :seller_id,
21
- :_input_charset
22
-
23
- validates_presence_of :service, :partner, :notify_url, :return_url, :sign, :sign_type, :subject, :out_trade_no, :payment_type
24
- validates_length_of :partner, :maximum => 16
25
- validates_length_of :notify_url, :return_url, :maximum => 190
26
- validates_length_of :show_url, :maximum => 400
27
- validates_length_of :body, :maximum => 1000
28
- validates_length_of :out_trade_no, :maximum => 64
29
- validates_length_of :payment_type, :maximum => 4
30
- validates_format_of :price, :total_fee,
31
- :with => /^[0-9]{1,9}\.[0-9]{1,2}$/,
32
- :allow_blank => true,
33
- :message => "format should be Number(13, 2)"
34
- validates_numericality_of :price, :total_fee,
35
- :greater_than_or_equal_to => 0.01,
36
- :less_than_or_equal_to => 100000000.00,
37
- :allow_blank => true,
38
- :message => "should between 0.01~100000000.00"
39
- validates_numericality_of :quantity,
40
- :only_integer => true,
41
- :greater_than => 0,
42
- :less_than => 1000000,
43
- :allow_blank => true,
44
- :message => "should be integer and between 1~999999"
45
- validates_inclusion_of :_input_charset, :in => %w(utf-8 gb2312), :message => "should be utf-8 or gb2312", :allow_blank => true
46
-
47
- validate do |am|
48
- am.errors[:money] << "price和total_fee不能同时出现" if am.repeat_money?
49
- am.errors[:money] << "price and total_fee can not both be blank" if am.money_blank?
50
- am.errors[:quantity] << "if price is not blank, must input quantity" if am.price_missing_quantity?
51
- am.errors[:seller] << "seller_email and seller_id can not both be blank" if am.seller_blank?
52
- am.errors[:sign] << "invalid sign" if am.invalid_sign?
53
- am.errors[:partner] << "not exist" if am.missing_partner?
54
- end
3
+ module Magpie
55
4
 
56
- def initialize(attributes = {})
57
- @attributes = attributes
58
- attributes.each do |name, value|
59
- send("#{name}=", value) if respond_to? name
60
- end
61
- end
5
+ class AlipayModel
62
6
 
63
- def repeat_money?
64
- self.price.to_s.length > 0 and self.total_fee.to_s.length > 0
65
- end
7
+ include Goose
8
+ include Mouse
66
9
 
67
- def price_missing_quantity?
68
- self.price.to_s.length > 0 and self.quantity.to_s.length == 0
69
- end
10
+ set_accounts_kind :alipay
70
11
 
71
- def missing_partner?
72
- return if self.partner.to_s.length == 0
73
- self.account == [] ? true : false
74
- end
12
+ attr_accessor :service, :partner, :notify_url, :return_url, :sign, :sign_type, :subject, :out_trade_no
75
13
 
76
- def seller_blank?
77
- self.seller_id.to_s.length == 0 and self.seller_email.to_s.length == 0
78
- end
14
+ attr_accessor :payment_type, :show_url, :body, :price, :total_fee, :quantity, :seller_email, :seller_id
79
15
 
80
- def money_blank?
81
- self.price.to_s.length == 0 and self.total_fee.to_s.length == 0
82
- end
16
+ attr_accessor :_input_charset
83
17
 
84
- def invalid_sign?
85
- attrs = @attributes.dup
86
- attrs.delete("sign")
87
- attrs.delete("sign_type")
88
- text = attrs.delete_if{ |k, v| v.to_s.length == 0 }.sort.collect{ |s| s[0] + "=" + URI.decode(s[1]) }.join("&") + self.key
89
- self.sign == Digest::MD5.hexdigest(text) ? false : true
90
- end
18
+ goose_validate :presence_attributes, :length_attributes, :format_attributes do |am|
19
+ am.errors[:money] << "price和total_fee不能同时出现" if am.repeat_money?
20
+ am.errors[:money] << "price and total_fee can not both be blank" if am.money_blank?
21
+ am.errors[:quantity] << "if price is not blank, must input quantity" if am.price_missing_quantity?
22
+ am.errors[:seller] << "seller_email and seller_id can not both be blank" if am.seller_blank?
23
+ am.errors[:sign] << "invalid sign" if am.invalid_sign?
24
+ am.errors[:partner] << "not exist" if !am.partner.blank? and am.missing_partner?
25
+ am.errors[:_input_charset] << "should be utf-8 or gb2312" unless am._input_charset.blank? or %w(utf-8 gb2312).member?(am._input_charset)
26
+ end
91
27
 
92
- def account
93
- @account ||= self.class.accounts.assoc self.partner
94
- @account ||= []
95
- end
28
+ def repeat_money?
29
+ self.price.to_s.length > 0 and self.total_fee.to_s.length > 0
30
+ end
96
31
 
97
- def key
98
- self.account[1].to_s
99
- end
32
+ def price_missing_quantity?
33
+ self.price.to_s.length > 0 and self.quantity.blank?
34
+ end
100
35
 
101
- def self.accounts
102
- @accounts ||= YAML.load_file('test/partner.yml')['alipay'] if ENV['magpie'] == 'test'
103
- @accounts ||= Magpie.yml_db['alipay']
104
- end
36
+ def seller_blank?
37
+ self.seller_id.blank? and self.seller_email.blank?
38
+ end
105
39
 
40
+ def money_blank?
41
+ self.price.blank? and self.total_fee.blank?
42
+ end
106
43
 
107
- def notify
108
- @notify ||= notify_attrs.inject({ }){ |notify, attr|
109
- notify[attr] = self.send(attr)
110
- notify
111
- }.merge("sign_type" => sign_type, "sign" => notify_sign)
112
- end
44
+ def invalid_sign?
45
+ attrs = @attributes.dup
46
+ attrs.delete("sign")
47
+ attrs.delete("sign_type")
48
+ text = attrs.delete_if{ |k, v| v.blank? }.sort.collect{ |s| s[0] + "=" + URI.decode(s[1]) }.join("&") + self.key
49
+ self.sign == Digest::MD5.hexdigest(text) ? false : true
50
+ end
113
51
 
114
- def send_notify
115
- url = URI.parse notify_url
116
- res = Net::HTTP.post_form url, self.notify
117
- res.body
118
- end
52
+ def notify
53
+ @notify ||= notify_attrs.inject({ }){ |notify, attr|
54
+ notify[attr] = self.send(attr)
55
+ notify
56
+ }.merge("sign_type" => sign_type, "sign" => notify_sign)
57
+ end
119
58
 
120
- private
121
- def notify_id
122
- @notify_id ||= Time.now.to_i
123
- end
124
59
 
125
- def notify_time
126
- @notify_time ||= Time.now.strftime("%Y-%m-%d %H:%M:%S")
127
- end
60
+ private
128
61
 
129
- def notify_sign
130
- @notify_sign ||= Digest::MD5.hexdigest(notify_text).downcase
131
- end
62
+ def notify_id
63
+ @notify_id ||= Time.now.to_i
64
+ end
132
65
 
133
- def notify_text
134
- @notify_text ||= notify_attrs.sort.collect{ |attr|
135
- "#{attr}=#{self.send(attr)}"
136
- }.join("&") + self.key
137
- end
66
+ def notify_time
67
+ @notify_time ||= Time.now.strftime("%Y-%m-%d %H:%M:%S")
68
+ end
138
69
 
139
- def trade_no
140
- @trade_no ||= Time.now.to_i.to_s + rand(1000000).to_s
141
- end
70
+ def notify_sign
71
+ @notify_sign ||= Digest::MD5.hexdigest(notify_text).downcase
72
+ end
142
73
 
143
- def trade_status
144
- @trade_status ||= %w(TRADE_FINISHED TRADE_SUCCESS)[rand(2)]
145
- end
74
+ def notify_text
75
+ @notify_text ||= notify_attrs.sort.collect{ |attr|
76
+ "#{attr}=#{self.send(attr)}"
77
+ }.join("&") + self.key
78
+ end
79
+
80
+ def trade_no
81
+ @trade_no ||= Time.now.to_i.to_s + rand(1000000).to_s
82
+ end
146
83
 
147
- def notify_attrs
148
- @notify_attrs ||= %w{ notify_id
84
+ def trade_status
85
+ @trade_status ||= %w(TRADE_FINISHED TRADE_SUCCESS)[rand(2)]
86
+ end
87
+
88
+ def notify_attrs
89
+ @notify_attrs ||= %w{ notify_id
149
90
  notify_time
150
91
  trade_no
151
92
  out_trade_no
@@ -166,7 +107,35 @@ class AlipayModel
166
107
  gmt_refund
167
108
  use_coupon
168
109
  }.select{ |attr| self.respond_to?(attr, true) && self.send(attr).to_s.length > 0 }
169
- end
110
+ end
111
+
112
+
113
+ def presence_attributes
114
+ [:service, :partner, :notify_url, :return_url, :sign, :sign_type, :subject, :out_trade_no, :payment_type].each {|attr|
115
+ self.errors[attr] << "can't be blank" if self.send(attr).blank?
116
+ }
117
+ end
118
+
119
+ def length_attributes
120
+ errors[:partner] << length_error_msg(16) if self.partner.to_s.length > 16
121
+ [:notify_url, :return_url].each { |attr| self.errors[attr] << length_error_msg(190) if self.send(attr).to_s.length > 190}
122
+ errors[:show_url] << length_error_msg(400) if self.show_url.to_s.length > 400
123
+ errors[:body] << length_error_msg(1000) if body.to_s.length > 1000
124
+ errors[:out_trade_no] << length_error_msg(64) if out_trade_no.to_s.length > 64
125
+ errors[:payment_type] << length_error_msg(4) if payment_type.to_s.length > 4
126
+ end
170
127
 
128
+ def format_attributes
129
+ [:price, :total_fee].each { |attr|
130
+ self.errors[attr] << "format should be Number(13, 2)" unless self.send(attr).blank? or self.send(attr) =~ /^[0-9]{1,9}\.[0-9]{1,2}$/
131
+ }
132
+ self.errors[:quantity] << "should be integer and between 1~999999" unless self.quantity.blank? or self.quantity =~ /^[1-9]{1,6}$/
133
+ end
134
+
135
+ def length_error_msg(length)
136
+ "is too long (maximum is #{length} characters)"
137
+ end
138
+
139
+ end
171
140
 
172
141
  end