obi-wan 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 +4 -0
- data/Gemfile +6 -0
- data/Rakefile +2 -0
- data/example/subscription_example.rb +47 -0
- data/example/subscription_example_async.rb +57 -0
- data/lib/obi-wan/messages.rb +173 -0
- data/lib/obi-wan/type.rb +68 -0
- data/lib/obi-wan/version.rb +3 -0
- data/lib/obi-wan/xml-client.rb +64 -0
- data/lib/obi-wan.rb +5 -0
- data/obi-wan.gemspec +21 -0
- metadata +67 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'obi-wan'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
include ObiWan
|
5
|
+
include ObiWan::XmlClient::Functions
|
6
|
+
|
7
|
+
# setup
|
8
|
+
client = XmlClient.new({
|
9
|
+
:base_url => 'https://aol.xbus.qat.ps.aol.com/biz/api',
|
10
|
+
:dev_id => 'pb19dgbLSGltGDiW',
|
11
|
+
:cipherdata => 'hello',
|
12
|
+
:hexresult => 'e1c9e47be92b358510029b76aea4c5e9',
|
13
|
+
:sg => 'patchselfserviceads'
|
14
|
+
})
|
15
|
+
|
16
|
+
# tg is static for this example.
|
17
|
+
tg = 1
|
18
|
+
|
19
|
+
|
20
|
+
client.create_instrument(
|
21
|
+
tg,
|
22
|
+
Type::InstrumentInfo.new( :email => 'spencer@corp.aol.com', :nick_name => 'SP', :card_on_file_flag => true, :default_flag => false ),
|
23
|
+
Type::BillingAddress.new( :street1 => '1201 dean st', :city => 'nyc', :state => 'ny', :zip => 11206, :country => 'US'),
|
24
|
+
Type::Instrument.new( :account_number => 4444424444444440, :expiry_date => '2012-12-31T09:46:25.705Z', :last_four_digits => 5674, :payment_type => 'VISA_DEBIT' ),
|
25
|
+
Type::Name.new(:first_name => 'bob', :last_name => 'hope', :middle_name => 'robert', :honorific => 'mr'),
|
26
|
+
Type::Phone.new( :type => 'HOME', :number => 7032641272, :area_code => 703, :country_code => 001 ) )
|
27
|
+
|
28
|
+
payment_id = xml_to_hash( client.list_instruments(tg, "DETAIL") )[:response][:data][:listInstrumentsResponse][:result][:instruments][:Instrument][:id]
|
29
|
+
|
30
|
+
# I LOVE this offer!
|
31
|
+
1.upto(1) do
|
32
|
+
client.register_offer_subscription(
|
33
|
+
tg,
|
34
|
+
Type::RegisterOfferSubscription.new( :offer_id => 1003421, :event_source => rand(), :payment_instrument_id => payment_id, :contact_email => 'pants@example.com' )
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
sub = client.list_subscriptions(tg, nil, false)
|
39
|
+
offer_sub_ids = offer_subscription_id(sub)
|
40
|
+
|
41
|
+
# i've been scammed! refund me!
|
42
|
+
responses = []
|
43
|
+
offer_sub_ids.each do |o|
|
44
|
+
puts "Cancelling sub.. #{o}"
|
45
|
+
resp = client.cancel_offer(tg, o, 'CANCEL_OFFER_SUBSCRIPTION')
|
46
|
+
puts "Transaction failed: #{resp}" unless success? resp
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'obi-wan'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
include ObiWan
|
5
|
+
include ObiWan::XmlClient::Functions
|
6
|
+
|
7
|
+
# setup
|
8
|
+
client = XmlClient.new({
|
9
|
+
:debug => STDERR,
|
10
|
+
:base_url => 'https://aol.xbus.qat.ps.aol.com/biz/api',
|
11
|
+
:dev_id => 'pb19dgbLSGltGDiW',
|
12
|
+
:cipherdata => 'hello',
|
13
|
+
:hexresult => 'e1c9e47be92b358510029b76aea4c5e9',
|
14
|
+
:sg => 'patchselfserviceads'
|
15
|
+
})
|
16
|
+
|
17
|
+
# let's go asynchonous for only cancel_offer for some mass cancellation fun
|
18
|
+
client.set_asynchronous(:cancel_offer)
|
19
|
+
|
20
|
+
# tg is static for this example.
|
21
|
+
tg = 1
|
22
|
+
|
23
|
+
|
24
|
+
client.create_instrument(
|
25
|
+
tg,
|
26
|
+
Type::InstrumentInfo.new( :email => 'spencer@corp.aol.com', :nick_name => 'SP', :card_on_file_flag => true, :default_flag => false ),
|
27
|
+
Type::BillingAddress.new( :street1 => '1201 dean st', :city => 'nyc', :state => 'ny', :zip => 11206, :country => 'US'),
|
28
|
+
Type::Instrument.new( :account_number => 4444424444444440, :expiry_date => '2012-12-31T09:46:25.705Z', :last_four_digits => 5674, :payment_type => 'VISA_DEBIT' ),
|
29
|
+
Type::Name.new(:first_name => 'bob', :last_name => 'hope', :middle_name => 'robert', :honorific => 'mr'),
|
30
|
+
Type::Phone.new( :type => 'HOME', :number => 7032641272, :area_code => 703, :country_code => 001 ) )
|
31
|
+
|
32
|
+
payment_id = xml_to_hash( client.list_instruments(tg, "DETAIL") )[:response][:data][:listInstrumentsResponse][:result][:instruments][:Instrument][:id]
|
33
|
+
|
34
|
+
# I LOVE this offer!
|
35
|
+
1.upto(20) do
|
36
|
+
client.register_offer_subscription(
|
37
|
+
tg,
|
38
|
+
Type::RegisterOfferSubscription.new( :offer_id => 1003421, :event_source => rand(), :payment_instrument_id => payment_id, :contact_email => 'pants@example.com' )
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
sub = client.list_subscriptions(tg, nil, false)
|
43
|
+
offer_sub_ids = offer_subscription_id(sub)
|
44
|
+
|
45
|
+
# i've been scammed! refund me!
|
46
|
+
# since cancel is now async, do a time on ruby to see how much faster this goes
|
47
|
+
responses = []
|
48
|
+
offer_sub_ids.each do |o|
|
49
|
+
puts "Cancelling sub.. #{o}"
|
50
|
+
responses << client.cancel_offer(tg, o, 'CANCEL_OFFER_SUBSCRIPTION')
|
51
|
+
end
|
52
|
+
|
53
|
+
responses.each do |r|
|
54
|
+
r.join
|
55
|
+
resp = resp_to_xml(r.pop)
|
56
|
+
puts "Transaction failed: #{resp}" unless success? resp
|
57
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module ObiWan
|
4
|
+
class XmlClient
|
5
|
+
module Functions
|
6
|
+
def self.included(base)
|
7
|
+
base.module_eval { extend(Functions) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def xml_to_hash(node)
|
11
|
+
|
12
|
+
# this doesn't cover <a>bcdefg<h>pants</h></a>
|
13
|
+
# luckily, obi doesn't do insane things like this
|
14
|
+
|
15
|
+
if node.elements.empty?
|
16
|
+
result = node.text
|
17
|
+
else
|
18
|
+
result = {}
|
19
|
+
node.elements.each do |c|
|
20
|
+
key = c.name.to_sym
|
21
|
+
val = xml_to_hash(c)
|
22
|
+
|
23
|
+
if result[key].nil?
|
24
|
+
result[key] = val
|
25
|
+
elsif result[key].is_a? Array
|
26
|
+
result[key] << val
|
27
|
+
else
|
28
|
+
result[key] = [ result[key], val ]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def resp_to_xml(resp)
|
36
|
+
Nokogiri::XML(resp.body)
|
37
|
+
end
|
38
|
+
|
39
|
+
def success?(response)
|
40
|
+
result = response.search("statusCode")
|
41
|
+
!result.nil? and result.text.to_i / 100 === 2
|
42
|
+
end
|
43
|
+
|
44
|
+
def offer_subscription_id(response)
|
45
|
+
puts response.xpath("/")
|
46
|
+
response.xpath("//offerSubscriptionId/value")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module CancelOffer
|
51
|
+
def self.obi_request(offer_subscription_id, type, reason_text=nil, code=nil, language_code=nil)
|
52
|
+
builder = Nokogiri::XML::Builder.new do
|
53
|
+
request('xmlns' => 'http://biz.aol.com/schema/2006-12-18') {
|
54
|
+
cancelOffer {
|
55
|
+
cancelOfferDetail {
|
56
|
+
offerSubscriptionId offer_subscription_id
|
57
|
+
reason {
|
58
|
+
text reason_text
|
59
|
+
languageCode code
|
60
|
+
}
|
61
|
+
actionType type
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end.doc.to_xml
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ListSubscriptions
|
70
|
+
def self.obi_request(product_class_name, active_only)
|
71
|
+
builder = Nokogiri::XML::Builder.new do
|
72
|
+
request('xmlns' => 'http://biz.aol.com/schema/2006-12-18') {
|
73
|
+
listSubscriptions {
|
74
|
+
productClassName product_class_name
|
75
|
+
active_only active_only
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end.doc.to_xml
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module RegisterOfferSubscription
|
83
|
+
def self.obi_request(offer_subscription)
|
84
|
+
os = offer_subscription
|
85
|
+
builder = Nokogiri::XML::Builder.new do
|
86
|
+
request('xmlns' => 'http://biz.aol.com/schema/2006-12-18') {
|
87
|
+
registerOfferSubscription {
|
88
|
+
orderRequests {
|
89
|
+
OrderRequest {
|
90
|
+
offerId os.offer_id
|
91
|
+
|
92
|
+
# event_source is a unique order id, non numeric if desired
|
93
|
+
# can be something like timestamp-userid-random()
|
94
|
+
eventSource os.event_source
|
95
|
+
paymentInstrumentId os.payment_instrument_id
|
96
|
+
contactEmail os.contact_email
|
97
|
+
promoCode os.promo_code
|
98
|
+
tosAcceptedDate os.tos_accepted_date
|
99
|
+
tosName os.tos_name
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
end.doc.to_xml
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module ListInstruments
|
109
|
+
def self.obi_request(detail_level)
|
110
|
+
builder = Nokogiri::XML::Builder.new do
|
111
|
+
request('xmlns' => 'http://biz.aol.com/schema/2006-12-18') {
|
112
|
+
listInstruments {
|
113
|
+
paymentDetailLevel detail_level
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end.doc.to_xml
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module Instrument
|
121
|
+
def self.obi_request(info, billing_address, instrument, name, phone)
|
122
|
+
builder = Nokogiri::XML::Builder.new do
|
123
|
+
request('xmlns' => 'http://biz.aol.com/schema/2006-12-18') {
|
124
|
+
createInstrument {
|
125
|
+
email {
|
126
|
+
value info.email
|
127
|
+
}
|
128
|
+
instrument {
|
129
|
+
id_ info.id
|
130
|
+
nickName info.nick_name
|
131
|
+
defaultFlag info.default_flag
|
132
|
+
cardOnFileFlag info.card_on_file_flag
|
133
|
+
|
134
|
+
userInformation {
|
135
|
+
firstUserName {
|
136
|
+
firstName name.first_name
|
137
|
+
lastName name.last_name
|
138
|
+
middleName name.middle_name
|
139
|
+
honorific name.honorific
|
140
|
+
}
|
141
|
+
|
142
|
+
billingAddress {
|
143
|
+
street1 billing_address.street1
|
144
|
+
street2 billing_address.street2
|
145
|
+
city billing_address.city
|
146
|
+
state billing_address.state
|
147
|
+
zip billing_address.zip
|
148
|
+
country billing_address.country
|
149
|
+
}
|
150
|
+
|
151
|
+
eveningPhone {
|
152
|
+
countryCode phone.country_code
|
153
|
+
areaCode phone.area_code
|
154
|
+
number phone.number
|
155
|
+
type phone.type
|
156
|
+
}
|
157
|
+
}
|
158
|
+
paymentInstrument {
|
159
|
+
accountNumber instrument.account_number
|
160
|
+
routingNumber instrument.routing_number
|
161
|
+
paymentType instrument.payment_type
|
162
|
+
expiryDate instrument.expiry_date
|
163
|
+
lastFourDigits instrument.last_four_digits
|
164
|
+
pinNumber instrument.pin_number
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
end.doc.to_xml
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/lib/obi-wan/type.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module ObiWan
|
2
|
+
module Type
|
3
|
+
class Builder
|
4
|
+
def initialize(init={})
|
5
|
+
init.keys.each do |attr|
|
6
|
+
self.send("#{attr}=".to_sym, init[attr])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# some api calls have misc data. it's easier to group them as single classes
|
12
|
+
|
13
|
+
class RegisterOfferSubscription < Builder
|
14
|
+
attr_accessor :offer_id
|
15
|
+
attr_accessor :event_source
|
16
|
+
attr_accessor :payment_instrument_id
|
17
|
+
attr_accessor :contact_email
|
18
|
+
attr_accessor :discount_amount
|
19
|
+
attr_accessor :promo_code
|
20
|
+
attr_accessor :tos_accepted_date
|
21
|
+
attr_accessor :tos_name
|
22
|
+
end
|
23
|
+
|
24
|
+
class InstrumentInfo < Builder
|
25
|
+
attr_accessor :email
|
26
|
+
attr_accessor :id
|
27
|
+
attr_accessor :nick_name
|
28
|
+
attr_accessor :default_flag
|
29
|
+
attr_accessor :card_on_file_flag
|
30
|
+
end
|
31
|
+
|
32
|
+
# obi data types
|
33
|
+
|
34
|
+
class BillingAddress < Builder
|
35
|
+
attr_accessor :street1
|
36
|
+
attr_accessor :street2
|
37
|
+
attr_accessor :city
|
38
|
+
attr_accessor :state
|
39
|
+
attr_accessor :zip
|
40
|
+
attr_accessor :country
|
41
|
+
end
|
42
|
+
|
43
|
+
class Name < Builder
|
44
|
+
attr_accessor :first_name
|
45
|
+
attr_accessor :middle_name
|
46
|
+
attr_accessor :last_name
|
47
|
+
attr_accessor :honorific
|
48
|
+
end
|
49
|
+
|
50
|
+
class Phone < Builder
|
51
|
+
attr_accessor :country_code
|
52
|
+
attr_accessor :area_code
|
53
|
+
attr_accessor :number
|
54
|
+
attr_accessor :type
|
55
|
+
end
|
56
|
+
|
57
|
+
class Instrument < Builder
|
58
|
+
attr_accessor :account_number
|
59
|
+
attr_accessor :routing_number
|
60
|
+
attr_accessor :payment_type
|
61
|
+
attr_accessor :expiry_date
|
62
|
+
attr_accessor :last_four_digits
|
63
|
+
attr_accessor :pin_number
|
64
|
+
attr_accessor :authorize_email_address
|
65
|
+
attr_accessor :authorize_ip_address
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'obi-wan/messages'
|
2
|
+
require 'httpclient'
|
3
|
+
|
4
|
+
#
|
5
|
+
# XmlClient has methods and modules registered to it. Messages are in the messages/ dir.
|
6
|
+
# The first parameter to each call must be the target id for use in the url. The rest of the
|
7
|
+
# parameters are in the def per message.
|
8
|
+
#
|
9
|
+
# If you don't like the XML response, you can peform a to_hash on it.
|
10
|
+
#
|
11
|
+
module ObiWan
|
12
|
+
class XmlClient
|
13
|
+
|
14
|
+
@@DEFAULT_CALLS = {
|
15
|
+
:register_offer_subscription => RegisterOfferSubscription,
|
16
|
+
:create_instrument => Instrument,
|
17
|
+
:edit_instrument => Instrument,
|
18
|
+
:list_instruments => ListInstruments,
|
19
|
+
:list_subscriptions => ListSubscriptions,
|
20
|
+
:cancel_offer => CancelOffer
|
21
|
+
}
|
22
|
+
|
23
|
+
def register(method, message_handler, synchronous = true)
|
24
|
+
method = method.to_sym
|
25
|
+
|
26
|
+
self.send(:define_singleton_method, method) do |tg, *args|
|
27
|
+
query = {
|
28
|
+
:devId => @config[:dev_id],
|
29
|
+
:cipherdata => @config[:cipherdata],
|
30
|
+
:hexresult => @config[:hexresult],
|
31
|
+
:tg => tg,
|
32
|
+
:sg => @config[:sg]
|
33
|
+
}
|
34
|
+
|
35
|
+
header = [['Content-Type', 'text/xml'], ['Accept','*/*'], ['User-Agent', 'obi-wan-0.0.1']]
|
36
|
+
|
37
|
+
req = HTTPClient.new
|
38
|
+
req.debug_dev = @config[:debug] if @config[:debug]
|
39
|
+
|
40
|
+
if synchronous
|
41
|
+
res = req.request(:POST, @config[:base_url], query, message_handler.obi_request(*args), header)
|
42
|
+
return resp_to_xml(res)
|
43
|
+
else
|
44
|
+
return req.request_async(:POST, @config[:base_url], query, message_handler.obi_request(*args), header)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_asynchronous(method, target = @@DEFAULT_CALLS[method])
|
50
|
+
register method, @@DEFAULT_CALLS[method], false
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_synchronous(method, target = @@DEFAULT_CALLS[method])
|
54
|
+
register method, @@DEFAULT_CALLS[method], true
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(options={})
|
58
|
+
@config = options
|
59
|
+
@@DEFAULT_CALLS.each do |k,v|
|
60
|
+
register k, v, true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/obi-wan.rb
ADDED
data/obi-wan.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "obi-wan/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "obi-wan"
|
7
|
+
s.version = ObiWan::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["spencer.portee@patch.com"]
|
10
|
+
s.email = ["spencer.portee@patch.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{ruby wrapper to obi's xml api}
|
13
|
+
s.description = %q{ruby wrapper to obi's xml api}
|
14
|
+
|
15
|
+
s.rubyforge_project = "obi-wan"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: obi-wan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- spencer.portee@patch.com
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-28 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: ruby wrapper to obi's xml api
|
18
|
+
email:
|
19
|
+
- spencer.portee@patch.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- .gitignore
|
28
|
+
- Gemfile
|
29
|
+
- Rakefile
|
30
|
+
- example/subscription_example.rb
|
31
|
+
- example/subscription_example_async.rb
|
32
|
+
- lib/obi-wan.rb
|
33
|
+
- lib/obi-wan/messages.rb
|
34
|
+
- lib/obi-wan/type.rb
|
35
|
+
- lib/obi-wan/version.rb
|
36
|
+
- lib/obi-wan/xml-client.rb
|
37
|
+
- obi-wan.gemspec
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: ""
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project: obi-wan
|
62
|
+
rubygems_version: 1.6.2
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: ruby wrapper to obi's xml api
|
66
|
+
test_files: []
|
67
|
+
|