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,124 +1,89 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- class ChinabankModel
4
- include ActiveModel::Validations
3
+ module Magpie
4
+ class ChinabankModel
5
5
 
6
- # 商户编号
7
- attr_accessor :v_mid
6
+ include Goose
7
+ include Mouse
8
8
 
9
- # 订单编号
10
- attr_accessor :v_oid
9
+ set_accounts_kind :chinabank
11
10
 
12
- # 订单总金额
13
- attr_accessor :v_amount
11
+ attr_accessor :v_mid, :v_oid, :v_amount, :v_moneytype, :v_url, :v_md5info, :remark1, :remark2
14
12
 
15
- # 币种
16
- attr_accessor :v_moneytype
13
+ goose_validate :presence_attributes
17
14
 
18
- # 消费者完成购物后返回的商户页面,URL参数是以http://开头的完整URL地址
19
- attr_accessor :v_url
20
-
21
- # MD5校验码
22
- attr_accessor :v_md5info
23
-
24
- # 备注
25
- attr_accessor :remark1, :remark2
26
-
27
-
28
- validates_presence_of :v_mid, :v_oid, :v_amount, :v_moneytype, :v_url, :v_md5info
29
- validates_length_of :v_oid, :maximum => 64
30
- validates_length_of :v_url, :maximum => 200
31
- validates_format_of :v_amount, :with => /^[0-9]{1,6}\.[0-9]{1,2}$/, :message => "format should be Number(6, 2)", :allow_blank => true
32
-
33
- validate do |am|
34
- am.errors[:sign] << "invalid v_md5info" if am.invalid_sign?
35
- end
36
-
37
- def initialize(attributes = {})
38
- @attributes = attributes
39
- attributes.each do |name, value|
40
- send("#{name}=", value) if respond_to? name
15
+ goose_validate do |am|
16
+ am.errors[:sign] << "invalid v_md5info" if am.invalid_sign?
17
+ am.errors[:v_oid] << "is too long (maximum is 64 characters)" if am.v_oid.to_s.length > 64
18
+ am.errors[:v_url] << "is too long (maximum is 200 characters)" if am.v_url.to_s.length > 200
19
+ am.errors[:v_amount] << "format should be Number(6, 2)" unless am.v_amount =~ /^[0-9]{1,6}\.[0-9]{1,2}$/ or am.v_amount.blank?
20
+ am.errors[:v_mid] << "商户编号不存在" if !am.partner.blank? and am.missing_partner?
41
21
  end
42
- end
43
22
 
44
- def invalid_sign?
45
- text = @attributes["v_amount"]+@attributes["v_moneytype"]+@attributes["v_oid"]+@attributes["v_mid"]+@attributes["v_url"]+self.key
46
- self.sign == Digest::MD5.hexdigest(text) ? false : true
23
+ def invalid_sign?
24
+ text = @attributes["v_amount"]+@attributes["v_moneytype"]+@attributes["v_oid"]+@attributes["v_mid"]+@attributes["v_url"]+self.key
25
+ self.sign.upcase == Digest::MD5.hexdigest(text).upcase ? false : true
47
26
  rescue => e
48
- true
49
- end
50
-
51
-
52
- def sign
53
- self.v_md5info
54
- end
55
-
56
- def partner
57
- self.v_mid
58
- end
59
-
60
- # 商家系统用来处理网银支付结果的url
61
- def notify_url
62
- self.v_url
63
- end
64
-
65
- def account
66
- @account ||= self.class.accounts.assoc self.partner
67
- @account ||= []
68
- end
27
+ true
28
+ end
69
29
 
30
+ def sign
31
+ self.v_md5info
32
+ end
70
33
 
71
- def key
72
- self.account[1].to_s
73
- end
34
+ def partner
35
+ self.v_mid
36
+ end
74
37
 
75
- def self.accounts
76
- @accounts ||= YAML.load_file('test/partner.yml')['chinabank'] if ENV['magpie'] == 'test'
77
- @accounts ||= Magpie.yml_db['chinabank']
78
- end
38
+ # 商家系统用来处理网银支付结果的url
39
+ def notify_url
40
+ self.v_url
41
+ end
79
42
 
80
- def notify
81
- @notify ||= { "v_oid" => v_oid,
82
- "v_pstatus" => v_pstatus,
83
- "v_amount" => v_amount,
84
- "v_pstring" => v_pstring,
85
- "v_pmode" => v_pmode,
86
- "v_moneytype" => v_moneytype,
87
- "v_md5str" => notify_sign,
88
- "remark1" => remark1,
89
- "remark2" => remark2
90
- }.delete_if { |k, v| v.to_s.length == 0}
91
- end
43
+ def notify
44
+ @notify ||= { "v_oid" => v_oid,
45
+ "v_pstatus" => v_pstatus,
46
+ "v_amount" => v_amount,
47
+ "v_pstring" => v_pstring,
48
+ "v_pmode" => v_pmode,
49
+ "v_moneytype" => v_moneytype,
50
+ "v_md5str" => notify_sign,
51
+ "remark1" => remark1,
52
+ "remark2" => remark2
53
+ }.delete_if { |k, v| v.to_s.length == 0}
54
+ end
92
55
 
93
- def send_notify
94
- url = URI.parse notify_url
95
- res = Net::HTTP.post_form url, self.notify
96
- res.body
97
- end
98
56
 
99
- private
100
- def notify_sign
101
- @notify_sign ||= Digest::MD5.hexdigest(notify_text).upcase
102
- end
57
+ private
58
+ def notify_sign
59
+ @notify_sign ||= Digest::MD5.hexdigest(notify_text).upcase
60
+ end
103
61
 
104
- def v_pstatus
105
- "20"
106
- end
62
+ def v_pstatus
63
+ "20"
64
+ end
107
65
 
108
- def v_pstring
109
- "支付完成"
110
- end
66
+ def v_pstring
67
+ "支付完成"
68
+ end
111
69
 
112
- def v_pmode
113
- %w{ 工商银行 招商银行 建设银行 光大银行 交通银行}[rand(5)]
114
- end
70
+ def v_pmode
71
+ banks = %w{ 工商银行 招商银行 建设银行 光大银行 交通银行}
72
+ banks[rand(banks.size)]
73
+ end
115
74
 
116
- def notify_text
117
- @notify_text ||= v_oid + v_pstatus + v_amount + v_moneytype + key
75
+ def notify_text
76
+ @notify_text ||= v_oid + v_pstatus + v_amount + v_moneytype + key
118
77
  rescue => e
119
- "invalid sign"
120
- end
78
+ "invalid sign"
79
+ end
121
80
 
81
+ def presence_attributes
82
+ [:v_mid, :v_oid, :v_amount, :v_moneytype, :v_url, :v_md5info].each { |attr|
83
+ errors[attr] << "can't be blank" if self.send(attr).blank?
84
+ }
85
+ end
122
86
 
87
+ end
123
88
 
124
89
  end
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Magpie
3
+ class Dung
4
+
5
+ def initialize(am)
6
+ @am = am
7
+ end
8
+
9
+ def total_fee
10
+ @total_fee ||= case @am
11
+ when AlipayModel; @am.total_fee
12
+ when TenpayModel; @am.total_fee.to_f/100
13
+ when ChinabankModel; @am.v_amount
14
+ end
15
+ end
16
+
17
+ def subject
18
+ @subjedt ||= case @am
19
+ when AlipayModel; @am.subject
20
+ when TenpayModel; @am.desc
21
+ when ChinabankModel; nil
22
+ end
23
+ end
24
+
25
+ def merchant
26
+ @merchant ||= "北京锋讯在线信息技术有限公司"
27
+ end
28
+
29
+ def trade_kind
30
+ @trade_kind ||= "即时到帐交易"
31
+ end
32
+
33
+ def trade_no
34
+ @trade_no ||= case @am
35
+ when AlipayModel; @am.out_trade_no
36
+ when TenpayModel; @am.transaction_id
37
+ when ChinabankModel; @am.v_oid
38
+ end
39
+ end
40
+
41
+ def price
42
+ @price ||= case @am
43
+ when AlipayModel; @am.price || @am.total_fee
44
+ when TenpayModel; @am.total_fee.to_f/100
45
+ when ChinabankModel; @am.v_amount
46
+ end
47
+ end
48
+
49
+ def notify_url
50
+ @notify_url ||= @am.notify_url
51
+ end
52
+
53
+ def notify_to_query
54
+ @nq ||= @am.notify.map{ |kv| "%s=%s" % kv }.join("&")
55
+ end
56
+
57
+ def kind
58
+ @name ||= case @am
59
+ when AlipayModel; "alipay"
60
+ when TenpayModel; "tenpay"
61
+ when ChinabankModel; "chinabank"
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,89 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Magpie
4
+
5
+ class TenpayModel
6
+ include Goose
7
+ include Mouse
8
+
9
+ set_accounts_kind :tenpay
10
+
11
+ attr_accessor :cmdno, :date, :bank_type, :desc, :purchaser_id, :bargainor_id, :transaction_id, :sp_billno
12
+
13
+ attr_accessor :total_fee, :fee_type, :return_url, :attach, :spbill_create_ip, :sign
14
+
15
+ goose_validate_presence_of :cmdno, :date, :bank_type, :desc, :bargainor_id, :transaction_id, :sp_billno
16
+
17
+ goose_validate_presence_of :total_fee, :fee_type, :return_url, :attach, :spbill_create_ip, :sign
18
+
19
+ goose_validate_format_of :transaction_id,
20
+ :allow_blank => true,
21
+ :with => /\A[0-9]{10}[0-9]{8}[0-9]{10}\Z/ ,
22
+ :msg => "格式错误,transaction_id为28位长的数值,其中前10位为商户网站编号(SPID),由财付通统一分配;之后8位为订单产生的日期,如20050415;最后10位商户需要保证一天内不同的事务(用户订购一次商品或购买一次服务),其ID不相同"
23
+
24
+ goose_validate_format_of :total_fee,
25
+ :allow_blank => true,
26
+ :with => /^\d+$/,
27
+ :msg => "格式错误,只能为数字,以分为单位,不允许包含任何字符"
28
+
29
+ goose_validate_format_of :spbill_create_ip,
30
+ :allow_blank => true,
31
+ :with => /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/
32
+
33
+ goose_validate_length_of :sp_billno, :max_length => 28, :allow_blank => true, :msg => "长度错误,应该在28个字符内"
34
+
35
+ goose_validate do |am|
36
+ am.errors[:fee_type] << "目前只支持人民币,请填1" unless am.fee_type.blank? or am.fee_type.to_s == "1"
37
+ am.errors[:sign] << "sign签名必须大写" unless am.sign.blank? or am.sign.upcase == am.sign
38
+ am.errors[:sign] << "invalid sign" if am.invalid_request_sign?
39
+ am.errors[:bargainor_id] << "商户号不存在" if !am.partner.blank? and am.missing_partner?
40
+ end
41
+
42
+
43
+ def invalid_request_sign?
44
+ text = %w(cmdno date bargainor_id transaction_id sp_billno total_fee fee_type return_url attach spbill_create_ip ).map{ |attr|
45
+ "#{attr}=#{@attributes[attr]}" unless @attributes[attr].blank?
46
+ }.join("&") + "&key=" + self.key.to_s
47
+ Digest::MD5.hexdigest(text).upcase != self.sign ? true : false
48
+ end
49
+
50
+ def partner
51
+ self.bargainor_id
52
+ end
53
+
54
+ def notify_sign
55
+ text = %w(cmdno pay_result date transaction_id sp_billno total_fee fee_type attach).map{ |attr|
56
+ "#{attr}=#{self.send(attr)}"
57
+ }.join("&") + "&key=" + self.key.to_s
58
+ Digest::MD5.hexdigest(text).upcase
59
+ end
60
+
61
+ def notify_url
62
+ self.return_url
63
+ end
64
+
65
+ def notify
66
+ @notify ||= { "cmdno" => cmdno,
67
+ "pay_result" => pay_result,
68
+ "date" => date,
69
+ "transaction_id" => transaction_id,
70
+ "sp_billno" => sp_billno,
71
+ "total_fee" => total_fee,
72
+ "fee_type" => fee_type,
73
+ "attach" => attach,
74
+ "sign" => notify_sign
75
+ }
76
+ end
77
+
78
+ def notify_string
79
+ @n_str ||= notify.map{ |kv| "%s=%s" % kv }.join("&")
80
+ end
81
+
82
+ def pay_result
83
+ @pay_result ||= "0"
84
+ end
85
+
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,12 @@
1
+
2
+
3
+ <div id="Info">
4
+ <div class="ErrorInfo">
5
+ <strong>请求失败</strong>
6
+ <ul>
7
+ <% @am.errors.each do |k, v| %>
8
+ <li><span class="qicon"><%= k %></span>:<span class="grey-info"><%= v.join("; ") %></span></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ </div>
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <title><%= @title %></title>
6
+ <style>
7
+ body {
8
+ font-family:arial,helvetica,clean,sans-serif;
9
+ font-size:12px;
10
+ font-size-adjust:none;
11
+ font-style:normal;
12
+ font-variant:normal;
13
+ font-weight:normal;
14
+ line-height:1.231;
15
+ }
16
+ #wrap{width:910px;text-align:left;margin:0 auto;}
17
+ .summary{background:#F0F3F5 none repeat scroll 0 0;margin-top:28px;padding:16px 22px 18px;;}
18
+ p.strong{font-weight:bold;}
19
+ #amount{color:#FF3300;font-size:30px;margin:0 5px;}
20
+ #amount sub{font-size:14px;vertical-align:baseline;}
21
+ .trade-title{margin:5px 0 0;}
22
+ .summary p{font-size:14px;}
23
+ #trade-list{padding-top:8px;}
24
+ #trade-list dd{clear:both;left:70px;position:relative;top:-18px;}
25
+ dt{font-weight:bold;}
26
+ .Pad{padding-top:20px;}
27
+
28
+ div#Info{border:1px solid #FFD0A8;}
29
+ div.ErrorInfo{background:transparent url(/images/error.gif) repeat scroll 0 0}
30
+ div#Info .ErrorInfo{background-color:#FFFFEE;background-position:20px 16px;background-repeat:no-repeat;width:auto;border:1px solid #FFF0A8}
31
+ div#Info strong{margin-left:65px;font-size:18px;padding:25px 0 6px 3px;display:block;}
32
+ div#Info ul{list-style-image:url(/images/q2.gif);margin:18px auto; }
33
+ div#Info li{text-align:center; font-size:16px;}
34
+ span.grey-info{color:#666666;font-size:13px;line-height:22px;margin-left:6px;}
35
+ </style>
36
+ </head>
37
+
38
+ <body>
39
+ <%= @yield %>
40
+ </body>
41
+
42
+ </html>
@@ -0,0 +1,34 @@
1
+
2
+ <div id="wrap">
3
+ <div class="summary dhide" id="s" style="display: block;">
4
+ <p class="strong">本次交易额<strong id="amount"><%= @dung.total_fee %><sub> 元</sub></strong>将支付给<span id="merchant"><%= @dung.merchant %></span>
5
+ </p>
6
+ <div class="trade-title">
7
+ <p>您购买的是:<label class="t-open" id="trade-switch"></label><a onmouseout="this.style.color='#07679c'" onmouseover="this.style.color='#ff7e00'" style="text-decoration: underline; color: rgb(7, 103, 156);" class="close-list" id="trade-exhibit"><%= @dung.subject %></a></p>
8
+ <dl id="trade-list" style="display: block;">
9
+ <% unless @dung.subject.blank? %>
10
+ <dt>商品名称:</dt>
11
+ <dd><%= @dung.subject %></dd>
12
+ <% end %>
13
+ <dt>交易对方:</dt>
14
+ <dd><%= @dung.merchant %></dd>
15
+ <dt>交易类型:</dt>
16
+ <dd><%= @dung.trade_kind %></dd>
17
+ <dt>订单号:</dt>
18
+ <dd><%= @dung.trade_no %></dd>
19
+ <dt>商品价格:</dt>
20
+ <dd><%= "%.2f" % @dung.price %> 元</dd>
21
+ </dl>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="Pad" id="comfirm">
26
+ <form action="/order/pay" method="post" target="_blank">
27
+ <input type="hidden" value="<%= @dung.notify_url%>" name="notify_url" />
28
+ <input type="hidden" value="<%= @dung.notify_to_query %>" name="notify" />
29
+ <input type="hidden" value="<%= @dung.kind %>" name="notify_kind" />
30
+ <input type="submit" value="确认无误, 付款"/>
31
+ </form>
32
+ </div>
33
+
34
+ </div>
@@ -1,22 +1,64 @@
1
+ # -*- coding: utf-8 -*-
1
2
  Gem::Specification.new do |s|
2
3
  s.name = "magpie"
3
- s.version = "0.8.6.2"
4
+ s.version = "0.8.8"
4
5
  s.platform = Gem::Platform::RUBY
5
6
  s.summary = "用ruby语言编写的支付平台测试沙盒"
6
7
 
7
8
  s.description = <<-EOF
8
- Magpie提供了支付宝(alipay), 网银在线(chinabank)的沙盒功能.使用Magpie, 开发人员可以测试商户系统提交到支付平台的参数是否正确, 并且当参数提交出错时, 可以获知详细的错误信息;
9
+ Magpie提供了支付宝(alipay), 网银在线(chinabank), 财付通(tenpay)的沙盒功能.使用Magpie, 开发人员可以测试商户系统提交到支付平台的参数是否正确, 并且当参数提交出错时, 可以获知详细的错误信息;
9
10
  Magpie模拟了各个支付平台的主动通知交互模式,这个功能可以使开发人员不必去支付平台的页面进行真实的支付,而通过Magpie就可以取得支付成功的效果,这样就可以轻松快速地对自己所开发的商户系统进行测试.
10
11
  EOF
11
12
 
12
- s.files = Dir["*/**/*"] - %w(lib/magpie.yml lib/mag) +
13
- %w(COPYING magpie.gemspec README.md Rakefile )
13
+ #s.files = Dir["{bin/*,lib/magpie/**/*,lib/*,lib/models/*,lib/middles/*,test/**/*}"] - %w(lib/magpie.yml lib/mag) +
14
+ %w(COPYING magpie.gemspec README.md Rakefile )
15
+ s.files =%w(
16
+ bin/mag
17
+ lib/magpie/mouse.rb
18
+ lib/magpie/server.rb
19
+ lib/magpie/utils.rb
20
+ lib/magpie/goose.rb
21
+ lib/magpie/rubber.rb
22
+ lib/middles
23
+ lib/models
24
+ lib/magpie.rb
25
+ lib/magpie
26
+ lib/apps.rb
27
+ lib/models/dung.rb
28
+ lib/models/alipay.rb
29
+ lib/models/chinabank.rb
30
+ lib/models/tenpay.rb
31
+ lib/middles/alipay.rb
32
+ lib/middles/snake.rb
33
+ lib/middles/chinabank.rb
34
+ lib/middles/tenpay.rb
35
+ lib/views
36
+ lib/views/fail.html.erb
37
+ lib/views/success.html.erb
38
+ lib/views/layouts
39
+ lib/views/layouts/app.html.erb
40
+ test/partner.yml
41
+ test/test_alipay.rb
42
+ test/test_tenpay.rb
43
+ test/helper.rb
44
+ test/test_utils.rb
45
+ test/test_chinabank.rb
46
+ test/test_dung.rb
47
+ test/test_snake.rb
48
+ test/test_object.rb
49
+ test/test.log
50
+ COPYING
51
+ magpie.gemspec
52
+ README.md
53
+ Rakefile
54
+ )
14
55
  s.bindir = 'bin'
15
56
  s.executables << 'mag'
16
57
  s.require_paths = ["lib"]
17
58
  s.has_rdoc = 'yard'
18
59
  s.extra_rdoc_files = ['README.md']
19
60
  s.test_files = Dir['test/test_*.rb']
61
+ s.homepage = 'http://github.com/baya/magpie'
20
62
  s.author = 'jiangguimin'
21
63
  s.email = 'kayak.jiang@gmail.com'
22
64
  end