gmo 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 +8 -0
- data/Gemfile +22 -0
- data/README.ja.md +54 -0
- data/README.md +54 -0
- data/Rakefile +15 -0
- data/autotest/discover.rb +1 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_alter_tran_change_order_auth_to_sale.yml +90 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_alter_tran_gets_data_about_order.yml +90 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_alter_tran_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_change_tran_gets_data_about_order.yml +90 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_change_tran_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_entry_tran_gets_data_about_a_transaction.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_entry_tran_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_exec_tran_gets_data_about_a_transaction.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_exec_tran_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_search_trade_gets_data_about_order.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_search_trade_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_search_trade_multi_gets_data_about_order.yml +34 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAPI/_search_trade_multi_got_error_if_missing_options.yml +34 -0
- data/fixtures/vcr_cassettes/GMO_Payment_ShopAndSiteAPI/_trade_card_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_delete_card_gets_data_about_a_card.yml +34 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_delete_member_gets_data_about_a_member.yml +34 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_save_card_gets_data_about_a_card.yml +34 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_save_member_gets_data_about_a_transaction.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_save_member_got_error_if_missing_options.yml +32 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_search_card_gets_data_about_a_card.yml +65 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_search_member_gets_data_about_a_member.yml +65 -0
- data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_update_member_gets_data_about_a_transaction.yml +32 -0
- data/gmo.gemspec +30 -0
- data/lib/gmo.rb +99 -0
- data/lib/gmo/errors.rb +51 -0
- data/lib/gmo/http_services.rb +77 -0
- data/lib/gmo/shop_and_site_api.rb +56 -0
- data/lib/gmo/shop_api.rb +218 -0
- data/lib/gmo/site_api.rb +146 -0
- data/lib/gmo/version.rb +3 -0
- data/spec/gmo/api_spec.rb +32 -0
- data/spec/gmo/error_spec.rb +25 -0
- data/spec/gmo/http_service_spec.rb +23 -0
- data/spec/gmo/shop_and_site_api_spec.rb +45 -0
- data/spec/gmo/shop_api_spec.rb +261 -0
- data/spec/gmo/site_api_spec.rb +140 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/config.example.yml +4 -0
- data/spec/support/config.yml +4 -0
- data/spec/support/config_loader.rb +2 -0
- data/spec/support/factory.rb +8 -0
- data/spec/support/vcr.rb +21 -0
- data/travis.yml +11 -0
- metadata +202 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://pt01.mul-pay.jp/payment/SaveMember.idPass
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: MemberID=null&MemberName=null&SiteID=tsite1&SitePass=1
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- ! '*/*'
|
12
|
+
User-Agent:
|
13
|
+
- Ruby
|
14
|
+
response:
|
15
|
+
status:
|
16
|
+
code: 200
|
17
|
+
message: OK
|
18
|
+
headers:
|
19
|
+
Date:
|
20
|
+
- Sat, 16 Feb 2013 04:56:31 GMT
|
21
|
+
Connection:
|
22
|
+
- close
|
23
|
+
Content-Type:
|
24
|
+
- text/plain;charset=Windows-31J
|
25
|
+
Transfer-Encoding:
|
26
|
+
- chunked
|
27
|
+
body:
|
28
|
+
encoding: US-ASCII
|
29
|
+
string: MemberID=null
|
30
|
+
http_version:
|
31
|
+
recorded_at: Sat, 16 Feb 2013 04:56:31 GMT
|
32
|
+
recorded_with: VCR 2.4.0
|
@@ -0,0 +1,65 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://pt01.mul-pay.jp/payment/SaveCard.idPass
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: MemberID=101&CardNo=4111111111111111&Expire=1405&SiteID=<SITE_ID>&SitePass=<SITE_PASS>
|
9
|
+
headers:
|
10
|
+
Accept-Encoding:
|
11
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
+
Accept:
|
13
|
+
- '*/*'
|
14
|
+
User-Agent:
|
15
|
+
- Ruby
|
16
|
+
response:
|
17
|
+
status:
|
18
|
+
code: 200
|
19
|
+
message: OK
|
20
|
+
headers:
|
21
|
+
Date:
|
22
|
+
- Sat, 16 Feb 2013 07:09:34 GMT
|
23
|
+
Connection:
|
24
|
+
- close
|
25
|
+
Content-Type:
|
26
|
+
- text/plain;charset=Windows-31J
|
27
|
+
Transfer-Encoding:
|
28
|
+
- chunked
|
29
|
+
body:
|
30
|
+
encoding: UTF-8
|
31
|
+
string: CardSeq=0&CardNo=*************111&Forward=2a99662
|
32
|
+
http_version:
|
33
|
+
recorded_at: Sat, 16 Feb 2013 07:09:34 GMT
|
34
|
+
- request:
|
35
|
+
method: post
|
36
|
+
uri: https://pt01.mul-pay.jp/payment/SearchCard.idPass
|
37
|
+
body:
|
38
|
+
encoding: UTF-8
|
39
|
+
string: MemberID=101&CardSeq=0&SeqMode=0&SiteID=<SITE_ID>&SitePass=<SITE_PASS>
|
40
|
+
headers:
|
41
|
+
Accept-Encoding:
|
42
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
43
|
+
Accept:
|
44
|
+
- '*/*'
|
45
|
+
User-Agent:
|
46
|
+
- Ruby
|
47
|
+
response:
|
48
|
+
status:
|
49
|
+
code: 200
|
50
|
+
message: OK
|
51
|
+
headers:
|
52
|
+
Date:
|
53
|
+
- Sat, 16 Feb 2013 07:09:34 GMT
|
54
|
+
Connection:
|
55
|
+
- close
|
56
|
+
Content-Type:
|
57
|
+
- text/plain;charset=Windows-31J
|
58
|
+
Transfer-Encoding:
|
59
|
+
- chunked
|
60
|
+
body:
|
61
|
+
encoding: UTF-8
|
62
|
+
string: CardSeq=0&DefaultFlag=0&CardName=&CardNo=*************111&Expire=1405&HolderName=&DeleteFlag=0
|
63
|
+
http_version:
|
64
|
+
recorded_at: Sat, 16 Feb 2013 07:09:34 GMT
|
65
|
+
recorded_with: VCR 2.4.0
|
@@ -0,0 +1,65 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://pt01.mul-pay.jp/payment/SaveMember.idPass
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: MemberID=101&MemberName=John+Smith&SiteID=<SITE_ID>&SitePass=<SITE_PASS>
|
9
|
+
headers:
|
10
|
+
Accept-Encoding:
|
11
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
+
Accept:
|
13
|
+
- '*/*'
|
14
|
+
User-Agent:
|
15
|
+
- Ruby
|
16
|
+
response:
|
17
|
+
status:
|
18
|
+
code: 200
|
19
|
+
message: OK
|
20
|
+
headers:
|
21
|
+
Date:
|
22
|
+
- Sat, 16 Feb 2013 06:55:41 GMT
|
23
|
+
Connection:
|
24
|
+
- close
|
25
|
+
Content-Type:
|
26
|
+
- text/plain;charset=Windows-31J
|
27
|
+
Transfer-Encoding:
|
28
|
+
- chunked
|
29
|
+
body:
|
30
|
+
encoding: UTF-8
|
31
|
+
string: MemberID=101
|
32
|
+
http_version:
|
33
|
+
recorded_at: Sat, 16 Feb 2013 06:55:41 GMT
|
34
|
+
- request:
|
35
|
+
method: post
|
36
|
+
uri: https://pt01.mul-pay.jp/payment/SearchMember.idPass
|
37
|
+
body:
|
38
|
+
encoding: UTF-8
|
39
|
+
string: MemberID=101&SiteID=<SITE_ID>&SitePass=<SITE_PASS>
|
40
|
+
headers:
|
41
|
+
Accept-Encoding:
|
42
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
43
|
+
Accept:
|
44
|
+
- '*/*'
|
45
|
+
User-Agent:
|
46
|
+
- Ruby
|
47
|
+
response:
|
48
|
+
status:
|
49
|
+
code: 200
|
50
|
+
message: OK
|
51
|
+
headers:
|
52
|
+
Date:
|
53
|
+
- Sat, 16 Feb 2013 06:55:42 GMT
|
54
|
+
Connection:
|
55
|
+
- close
|
56
|
+
Content-Type:
|
57
|
+
- text/plain;charset=Windows-31J
|
58
|
+
Transfer-Encoding:
|
59
|
+
- chunked
|
60
|
+
body:
|
61
|
+
encoding: UTF-8
|
62
|
+
string: MemberID=101&MemberName=John Smith&DeleteFlag=0
|
63
|
+
http_version:
|
64
|
+
recorded_at: Sat, 16 Feb 2013 06:55:41 GMT
|
65
|
+
recorded_with: VCR 2.4.0
|
data/fixtures/vcr_cassettes/GMO_Payment_SiteAPI/_update_member_gets_data_about_a_transaction.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://pt01.mul-pay.jp/payment/UpdateMember.idPass
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: MemberID=100&MemberName=John+Smith2&SiteID=<SITE_ID>&SitePass=<SITE_PASS>
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- ! '*/*'
|
12
|
+
User-Agent:
|
13
|
+
- Ruby
|
14
|
+
response:
|
15
|
+
status:
|
16
|
+
code: 200
|
17
|
+
message: OK
|
18
|
+
headers:
|
19
|
+
Date:
|
20
|
+
- Sat, 16 Feb 2013 05:29:21 GMT
|
21
|
+
Connection:
|
22
|
+
- close
|
23
|
+
Content-Type:
|
24
|
+
- text/plain;charset=Windows-31J
|
25
|
+
Transfer-Encoding:
|
26
|
+
- chunked
|
27
|
+
body:
|
28
|
+
encoding: US-ASCII
|
29
|
+
string: MemberID=100
|
30
|
+
http_version:
|
31
|
+
recorded_at: Sat, 16 Feb 2013 05:29:21 GMT
|
32
|
+
recorded_with: VCR 2.4.0
|
data/gmo.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gmo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "gmo"
|
8
|
+
gem.description = %q{Ruby client library for the GMO Payment Platform.}
|
9
|
+
gem.summary = %q{GMO Payment API client: Ruby client library for the GMO Payment Platform.}
|
10
|
+
gem.homepage = ""
|
11
|
+
gem.version = GMO::VERSION
|
12
|
+
|
13
|
+
gem.authors = ["Tatsuo Kaniwa"]
|
14
|
+
gem.email = ["tatsuo@kaniwa.biz"]
|
15
|
+
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.files = `git ls-files`.split("\n")
|
18
|
+
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
19
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
|
21
|
+
gem.extra_rdoc_files = ["README.md"]
|
22
|
+
gem.rdoc_options = ["--line-numbers", "--inline-source", "--title", "GMO"]
|
23
|
+
|
24
|
+
gem.add_runtime_dependency "rack"
|
25
|
+
gem.add_runtime_dependency "multi_json"
|
26
|
+
gem.add_development_dependency "rspec"
|
27
|
+
gem.add_development_dependency "rake"
|
28
|
+
gem.add_development_dependency "vcr"
|
29
|
+
gem.add_development_dependency "webmock"
|
30
|
+
end
|
data/lib/gmo.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
require 'gmo/errors'
|
6
|
+
require 'gmo/http_services'
|
7
|
+
require 'gmo/shop_api'
|
8
|
+
require 'gmo/site_api'
|
9
|
+
require 'gmo/shop_and_site_api'
|
10
|
+
require "gmo/version"
|
11
|
+
|
12
|
+
# Ruby client library for the GMO Payment Platform.
|
13
|
+
# Copyright 2013 Tatsuo Kaniwa
|
14
|
+
# tatsuo[at]kaniwa.biz
|
15
|
+
|
16
|
+
module GMO
|
17
|
+
|
18
|
+
module Payment
|
19
|
+
|
20
|
+
DEV_SERVER = "pt01.mul-pay.jp"
|
21
|
+
# TODO: Set production server
|
22
|
+
PRO_SERVER = "pt01.mul-pay.jp"
|
23
|
+
|
24
|
+
class API
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@development = options[:development] || true
|
28
|
+
end
|
29
|
+
attr_reader :development
|
30
|
+
|
31
|
+
def api(path, args = {}, verb = "post", options = {}, &error_checking_block)
|
32
|
+
# Setup args for make_request
|
33
|
+
path = "/payment/#{path}" unless path =~ /^\//
|
34
|
+
options.merge!({ "development" => @development })
|
35
|
+
# Make request via the provided service
|
36
|
+
result = GMO.make_request path, args, verb, options
|
37
|
+
# Check for any 500 server errors before parsing the body
|
38
|
+
if result.status >= 500
|
39
|
+
error_detail = {
|
40
|
+
:http_status => result.status.to_i,
|
41
|
+
:body => result.body,
|
42
|
+
}
|
43
|
+
raise GMO::Payment::ServerError.new(result.body, error_detail)
|
44
|
+
end
|
45
|
+
# Parse the body as Query string
|
46
|
+
body = response = Rack::Utils.parse_nested_query result.body.to_s
|
47
|
+
# Check for errors if provided a error_checking_block
|
48
|
+
yield(body) if error_checking_block
|
49
|
+
# Return result
|
50
|
+
if options[:http_component]
|
51
|
+
result.send options[:http_component]
|
52
|
+
else
|
53
|
+
body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# gmo.get_request("EntryTran.idPass", {:foo => "bar"})
|
58
|
+
# GET /EntryTran.idPass with params foo=bar
|
59
|
+
def get_request(name, args = {}, options = {})
|
60
|
+
api_call(name, args, "get", options)
|
61
|
+
end
|
62
|
+
alias :get! :get_request
|
63
|
+
|
64
|
+
# gmo.post_request("EntryTran.idPass", {:foo => "bar"})
|
65
|
+
# POST /EntryTran.idPass with params foo=bar
|
66
|
+
def post_request(name, args = {}, options = {})
|
67
|
+
api_call(name, args, "post", options)
|
68
|
+
end
|
69
|
+
alias :post! :post_request
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def api_call(*args)
|
74
|
+
raise "Called abstract method: api_call"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class ShopAPI < API
|
80
|
+
include ShopAPIMethods
|
81
|
+
end
|
82
|
+
|
83
|
+
class SiteAPI < API
|
84
|
+
include SiteAPIMethods
|
85
|
+
end
|
86
|
+
|
87
|
+
class ShopAndSiteAPI < API
|
88
|
+
include ShopAndSiteAPIMethods
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set up the http service GMO methods used to make requests
|
94
|
+
def self.http_service=(service)
|
95
|
+
self.send :include, service
|
96
|
+
end
|
97
|
+
|
98
|
+
GMO.http_service = NetHTTPService
|
99
|
+
end
|
data/lib/gmo/errors.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# begin
|
2
|
+
# # do something...
|
3
|
+
# rescue GMO::Payment::APIError => e
|
4
|
+
# puts e.response_body
|
5
|
+
# # => ErrCode=hoge&ErrInfo=hoge
|
6
|
+
# puts e.error_info
|
7
|
+
# # {"ErrCode"=>"hoge", "ErrInfo"=>"hoge"}
|
8
|
+
# end
|
9
|
+
|
10
|
+
module GMO
|
11
|
+
|
12
|
+
class GMOError < StandardError; end
|
13
|
+
|
14
|
+
module Payment
|
15
|
+
class Error < ::GMO::GMOError
|
16
|
+
attr_accessor :error_info, :response_body
|
17
|
+
|
18
|
+
def initialize(response_body = "", error_info = nil)
|
19
|
+
if response_body && response_body.is_a?(String)
|
20
|
+
self.response_body = response_body.strip
|
21
|
+
else
|
22
|
+
self.response_body = ''
|
23
|
+
end
|
24
|
+
if error_info.nil?
|
25
|
+
begin
|
26
|
+
error_info = Rack::Utils.parse_nested_query(response_body.to_s)
|
27
|
+
rescue
|
28
|
+
error_info ||= {}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
self.error_info = error_info
|
32
|
+
message = self.response_body
|
33
|
+
super(message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ServerError < Error
|
38
|
+
end
|
39
|
+
|
40
|
+
class APIError < Error
|
41
|
+
def initialize(error_info = {})
|
42
|
+
self.error_info = error_info
|
43
|
+
self.response_body = "ErrCode=#{error_info["ErrCode"]}&ErrInfo=#{error_info["ErrInfo"]}"
|
44
|
+
message = self.response_body
|
45
|
+
super(message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module GMO
|
2
|
+
class Response
|
3
|
+
|
4
|
+
attr_reader :status, :body, :headers
|
5
|
+
def initialize(status, body, headers)
|
6
|
+
@status = status
|
7
|
+
@body = body
|
8
|
+
@headers = headers
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module HTTPService
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.class_eval do
|
17
|
+
def self.server(options = {})
|
18
|
+
options[:development] ? Payment::DEV_SERVER : Payment::PRO_SERVER
|
19
|
+
end
|
20
|
+
end# end class_eval
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module NetHTTPService
|
25
|
+
def self.included(base)
|
26
|
+
base.class_eval do
|
27
|
+
require "net/http" unless defined?(Net::HTTP)
|
28
|
+
require "net/https"
|
29
|
+
|
30
|
+
include GMO::HTTPService
|
31
|
+
|
32
|
+
def self.make_request(path, args, verb, options = {})
|
33
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
34
|
+
|
35
|
+
http = create_http(server(options), options)
|
36
|
+
http.use_ssl = true
|
37
|
+
|
38
|
+
result = http.start do |http|
|
39
|
+
response, body = if verb == "post"
|
40
|
+
http.post(path, encode_params(args))
|
41
|
+
else
|
42
|
+
http.get("#{path}?#{encode_params(args)}")
|
43
|
+
end
|
44
|
+
GMO::Response.new(response.code.to_i, response.body, response)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def self.encode_params(param_hash)
|
51
|
+
((param_hash || {}).collect do |key_and_value|
|
52
|
+
key_and_value[1] = MultiJson.dump(key_and_value[1]) if key_and_value[1].class != String
|
53
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
54
|
+
end).join("&")
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.create_http(server, options)
|
58
|
+
if options[:proxy]
|
59
|
+
proxy = URI.parse(options[:proxy])
|
60
|
+
http = Net::HTTP.new \
|
61
|
+
server, 443,
|
62
|
+
proxy.host, proxy.port,
|
63
|
+
proxy.user, proxy.password
|
64
|
+
else
|
65
|
+
http = Net::HTTP.new server, 443
|
66
|
+
end
|
67
|
+
if options[:timeout]
|
68
|
+
http.open_timeout = options[:timeout]
|
69
|
+
http.read_timeout = options[:timeout]
|
70
|
+
end
|
71
|
+
http
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|