px 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50752bce2cd1db2c72c0b839fef08f4e510b1d52
4
+ data.tar.gz: 035c5445a531812d74b358ac36f845c8ead5146c
5
+ SHA512:
6
+ metadata.gz: 44a4531f2f41b38911aade34f14993159f65ffbd90c3b1811df0a70375a06430d0207721b6c19ef3e13529c6df54cd8a0a24850c9a1bac00018eb7aa3b74470a
7
+ data.tar.gz: a641d4c79afb30cf14aa77625340f0696c02ddae092b8e23ccae51977dcf17cd3cb639f96d5cc05268edcc96cf6e3eea94b7cab6d502c91c69656372ddcbde66
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Cyril David
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ test/px_test.rb: .PHONY
2
+ cutest test/px_test.rb
3
+
4
+ test: test/px_test.rb
5
+
6
+ test_integration:
7
+ TEST_INTEGRATION=1 cutest test/px_test.rb
8
+
9
+ .PHONY:
@@ -0,0 +1,21 @@
1
+ ## PX: paymentexpress library
2
+
3
+ ```ruby
4
+
5
+ url = PX.request(...)
6
+ # redirect to the url / iframe target it
7
+
8
+ # get redirected to a URL with `result` parameter then
9
+ data = PX.response(response: result)
10
+
11
+ # you can now use this as your customer token
12
+ # if you want to charge the same card in the future
13
+ # for recurring and/or pay per use type scenarios.
14
+ data[:token]
15
+
16
+ # Sometime in the future:
17
+ PX.post(amount: 1, token: token)
18
+
19
+ ```
20
+
21
+ ### LICENSE: MIT
@@ -0,0 +1,207 @@
1
+ require "hache"
2
+ require "logger"
3
+ require "mote"
4
+ require "requests"
5
+ require "xmlsimple"
6
+
7
+ module PX
8
+ UID = ENV.fetch("PX_UID")
9
+ KEY = ENV.fetch("PX_KEY")
10
+
11
+ SUCCESS_URL = ENV.fetch("PX_SUCCESS_URL")
12
+ FAIL_URL = ENV.fetch("PX_FAIL_URL")
13
+
14
+ TYPES = [
15
+ TYPE_AUTH = "Auth",
16
+ TYPE_PURCHASE = "Purchase",
17
+ TYPE_COMPLETE = "Complete",
18
+ TYPE_VALIDATE = "Validate",
19
+ TYPE_REFUND = "Refund",
20
+ ]
21
+
22
+ def self.request(data)
23
+ Util.post(Request, data)
24
+ end
25
+
26
+ def self.response(data)
27
+ Util.post(Response, data)
28
+ end
29
+
30
+ def self.post(data)
31
+ Util.post(PxPost, data)
32
+ end
33
+
34
+ module Util
35
+ def self.post(strategy, data)
36
+ xml = strategy.build(data)
37
+
38
+ begin
39
+ response = Requests.request("POST", strategy::URL, data: xml)
40
+
41
+ strategy.parse(response.body)
42
+ rescue Requests::Error => err
43
+ log(:error, strategy.name, err.inspect)
44
+
45
+ return nil
46
+ end
47
+ end
48
+
49
+ def self.logger=(logger)
50
+ @logger = logger
51
+ end
52
+ @logger = Logger.new(STDERR)
53
+
54
+ def self.log(type, namespace, text)
55
+ @logger.send(type, "%s -- %s" % [namespace, text])
56
+ end
57
+ end
58
+
59
+ module Request
60
+ extend Mote::Helpers
61
+
62
+ XML = File.expand_path("../xml/request.xml", __FILE__)
63
+
64
+ # Endpoint for Request / Response related posts
65
+ URL = "https://sec.paymentexpress.com/pxpay/pxaccess.aspx"
66
+
67
+ # <Request valid="1">
68
+ # <URI>https://sec.paymentexpress.com/pxmi3/XXXX</URI>
69
+ # </Request>
70
+ def self.parse(xml)
71
+ dict = XmlSimple.xml_in(xml, forcearray: false)
72
+
73
+ PX::Util.log(:info, self.name, dict.inspect)
74
+
75
+ if dict["valid"] == "1" && dict["URI"]
76
+ dict["URI"]
77
+ end
78
+ end
79
+
80
+ def self.build(data)
81
+ params = {
82
+ uid: UID,
83
+ key: KEY,
84
+
85
+ # = Required parameters with smart defaults:
86
+ currency: data.fetch(:currency, "USD"),
87
+
88
+ # == Optimized for Token Billing defaults
89
+ # (AUTH and Amount = 1 only)
90
+ type: data.fetch(:type, TYPE_AUTH),
91
+ amount: data.fetch(:amount, "1"),
92
+
93
+ # = Optional parameters, but we make it mandatory
94
+ # in this library
95
+ email: data.fetch(:email),
96
+
97
+ # == Really optional (both in API and in lib)
98
+ ref: data.fetch(:ref, ""),
99
+ data1: data.fetch(:data1, ""),
100
+ data2: data.fetch(:data2, ""),
101
+ data3: data.fetch(:data3, ""),
102
+ txn_id: data.fetch(:txn_id, ""),
103
+
104
+ # == Given that we optimize for Token billing, we
105
+ # default to `true`.
106
+ add_bill_card: data.fetch(:add_bill_card, true),
107
+
108
+ success_url: data.fetch(:success_url, SUCCESS_URL),
109
+ fail_url: data.fetch(:fail_url, FAIL_URL),
110
+
111
+ this: Hache,
112
+ }
113
+
114
+ mote(XML, params)
115
+ end
116
+ end
117
+
118
+ module Response
119
+ extend Mote::Helpers
120
+
121
+ XML = File.expand_path("../xml/response.xml", __FILE__)
122
+
123
+ # Endpoint for Request / Response related posts
124
+ URL = "https://sec.paymentexpress.com/pxpay/pxaccess.aspx"
125
+
126
+ def self.parse(xml)
127
+ dict = XmlSimple.xml_in(xml, forcearray: false)
128
+
129
+ PX::Util.log(:info, self.name, dict.inspect)
130
+
131
+ if dict["valid"] == "1" && dict["Success"] == "1"
132
+ # Provide a more agnostic term so outside code
133
+ # fetching it won't look too tied to PX.
134
+ dict[:token] = dict["DpsBillingId"]
135
+
136
+ return dict
137
+ end
138
+ end
139
+
140
+ def self.build(data)
141
+ params = {
142
+ uid: UID,
143
+ key: KEY,
144
+
145
+ # The only key we require for PX::Response
146
+ # is `response` which is obtained after
147
+ # a successful redirect with the `result`
148
+ # query string parameter.
149
+ #
150
+ response: data.fetch(:response),
151
+
152
+ this: Hache,
153
+ }
154
+
155
+ mote(XML, params)
156
+ end
157
+ end
158
+
159
+ module PxPost
160
+ extend Mote::Helpers
161
+
162
+ USERNAME = ENV.fetch("PX_USERNAME")
163
+ PASSWORD = ENV.fetch("PX_PASSWORD")
164
+
165
+ # PX Post endpoint; used in conjunction with DpsBillingId
166
+ # and the Token billing strategy.
167
+ URL = "https://sec.paymentexpress.com/pxpost.aspx"
168
+
169
+ XML = File.expand_path("../xml/post.xml", __FILE__)
170
+
171
+ def self.parse(xml)
172
+ dict = XmlSimple.xml_in(xml, forcearray: false)
173
+
174
+ PX::Util.log(:info, self.name, dict.inspect)
175
+
176
+ return unless data = dict["Transaction"]
177
+
178
+ if data["success"] == "1" && data["responseText"] == "APPROVED"
179
+ return data
180
+ end
181
+ end
182
+
183
+ def self.build(data)
184
+ params = {
185
+ uid: USERNAME,
186
+ key: PASSWORD,
187
+
188
+ # required parameters
189
+ amount: data.fetch(:amount),
190
+
191
+ # required, but with sane defaults.
192
+ currency: data.fetch(:currency, "USD"),
193
+ type: data.fetch(:type, TYPE_PURCHASE),
194
+
195
+ # mandatory recommendations even though
196
+ # they're optional in the API.
197
+ txn_id: data.fetch(:txn_id, ""),
198
+ ref: data.fetch(:ref, ""),
199
+ token: data.fetch(:token),
200
+
201
+ this: Hache,
202
+ }
203
+
204
+ mote(XML, params)
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,10 @@
1
+ <Txn>
2
+ <PostUsername>{{ uid }}</PostUsername>
3
+ <PostPassword>{{ key }}</PostPassword>
4
+ <Amount>{{ "%0.2f" % amount }}</Amount>
5
+ <InputCurrency>{{ currency }}</InputCurrency>
6
+ <TxnType>{{ type }}</TxnType>
7
+ <TxnId>{{ this.h(txn_id) }}</TxnId>
8
+ <MerchantReference>{{ this.h(ref) }}</MerchantReference>
9
+ <DpsBillingId>{{ this.h(token) }}</DpsBillingId>
10
+ </Txn>
@@ -0,0 +1,16 @@
1
+ <GenerateRequest>
2
+ <PxPayUserId>{{ uid }}</PxPayUserId>
3
+ <PxPayKey>{{ key }}</PxPayKey>
4
+ <MerchantReference>{{ ref }}</MerchantReference>
5
+ <TxnType>{{ type }}</TxnType>
6
+ <AmountInput>{{ "%0.2f" % amount }}</AmountInput>
7
+ <CurrencyInput>{{ currency }}</CurrencyInput>
8
+ <TxnData1>{{ this.h(data1) }}</TxnData1>
9
+ <TxnData2>{{ this.h(data2) }}</TxnData2>
10
+ <TxnData3>{{ this.h(data3) }}</TxnData3>
11
+ <EmailAddress>{{ this.h(email) }}</EmailAddress>
12
+ <TxnId>{{ this.h(txn_id) }}</TxnId>
13
+ <EnableAddBillCard>{{ add_bill_card ? 1 : 0 }}</EnableAddBillCard>
14
+ <UrlSuccess>{{ success_url }}</UrlSuccess>
15
+ <UrlFail>{{ fail_url }}</UrlFail>
16
+ </GenerateRequest>
@@ -0,0 +1,5 @@
1
+ <ProcessResponse>
2
+ <PxPayUserId>{{ uid }}</PxPayUserId>
3
+ <PxPayKey>{{ key }}</PxPayKey>
4
+ <Response>{{ response }}</Response>
5
+ </ProcessResponse>
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "px"
5
+ s.version = "0.0.1"
6
+ s.summary = "PX library"
7
+ s.description = "PaymentExpress 1.0 integration library"
8
+ s.authors = ["Cyril David"]
9
+ s.email = ["cyx@cyx.is"]
10
+ s.homepage = "http://cyx.is"
11
+ s.files = Dir[
12
+ "LICENSE",
13
+ "README*",
14
+ "Makefile",
15
+ "lib/*.rb",
16
+ "lib/xml/*.xml",
17
+ "*.gemspec",
18
+ "test/*.*",
19
+ ]
20
+
21
+ s.license = "MIT"
22
+
23
+ s.add_dependency "requests", "~> 1.0"
24
+ s.add_dependency "xml-simple", "~> 1.1"
25
+ s.add_dependency "mote", "~> 1.1"
26
+ s.add_dependency "hache", "~> 1.1"
27
+ s.add_development_dependency "cutest", "~> 1.2"
28
+ end
@@ -0,0 +1,140 @@
1
+ require "cutest"
2
+ require "securerandom"
3
+ require "open-uri"
4
+
5
+ ENV["PX_SUCCESS_URL"] = "http://openredis.com/"
6
+ ENV["PX_FAIL_URL"] = "http://openredis.com/"
7
+
8
+ require_relative "../lib/px"
9
+
10
+ # This will hit the live URL, so you'll have to have
11
+ # the proper UID / KEY to be able to test this properly.
12
+ test "PX.request" do
13
+ data = {
14
+ email: "cyx@cyx.is",
15
+ txn_id: SecureRandom.hex(8),
16
+ ref: SecureRandom.hex(16),
17
+ type: PX::TYPE_AUTH,
18
+ amount: 1,
19
+ currency: "USD",
20
+ add_bill_card: true
21
+ }
22
+
23
+ # Since we're passing trash data, we expect it
24
+ # to not give us a URI response.
25
+ uri = URI(PX.request(data))
26
+
27
+ assert_equal "https", uri.scheme
28
+ assert_equal "sec.paymentexpress.com", uri.host
29
+
30
+ assert open(uri.to_s).read.include?("Credit Card Payment")
31
+
32
+ end if ENV["TEST_INTEGRATION"]
33
+
34
+ test "parse request (valid)" do
35
+ request = <<-EOT
36
+ <Request valid="1">
37
+ <URI>https://sec.paymentexpress.com/pxmi3/XXXX</URI>
38
+ </Request>
39
+ EOT
40
+
41
+ expected = "https://sec.paymentexpress.com/pxmi3/XXXX"
42
+
43
+ assert_equal expected, PX::Request.parse(request)
44
+ end
45
+
46
+ test "parse response (valid)" do
47
+ response = <<-EOT
48
+ <Response valid="1">
49
+ <Success>1</Success>
50
+ <TxnType>Purchase</TxnType>
51
+ <CurrencyInput>NZD</CurrencyInput>
52
+ <MerchantReference>Purchase Example</MerchantReference>
53
+ <TxnData1></TxnData1>
54
+ <TxnData2></TxnData2>
55
+ <TxnData3></TxnData3>
56
+ <AuthCode>113837</AuthCode>
57
+ <CardName>Visa</CardName>
58
+ <CardHolderName>CARDHOLDER NAME</CardHolderName>
59
+ <CardNumber>411111........11</CardNumber>
60
+ <DateExpiry>1111</DateExpiry>
61
+ <ClientInfo>192.168.1.111</ClientInfo>
62
+ <TxnId>P03E57DA8A9DD700</TxnId>
63
+ <EmailAddress></EmailAddress>
64
+ <DpsTxnRef>000000060495729b</DpsTxnRef>
65
+ <BillingId></BillingId>
66
+ <DpsBillingId></DpsBillingId>
67
+ <AmountSettlement>1.00</AmountSettlement>
68
+ <CurrencySettlement>NZD</CurrencySettlement>
69
+ <DateSettlement>20100924</DateSettlement>
70
+ <TxnMac>BD43E619</TxnMac>
71
+ <ResponseText>APPROVED</ResponseText>
72
+ <CardNumber2></CardNumber2>
73
+ <Cvc2ResultCode>M</Cvc2ResultCode>
74
+ </Response>
75
+ EOT
76
+
77
+ dict = PX::Response.parse(response)
78
+
79
+ assert dict.kind_of?(Hash)
80
+ end
81
+
82
+ test "response build" do
83
+ xml = PX::Response.build(response: "foo")
84
+
85
+ dict = XmlSimple.xml_in(xml, forcearray: false)
86
+
87
+ assert_equal PX::UID, dict["PxPayUserId"]
88
+ assert_equal PX::KEY, dict["PxPayKey"]
89
+ assert_equal "foo", dict["Response"]
90
+ end
91
+
92
+ test "response live" do
93
+ data = {
94
+ response: "00000100491234332513213fb881cc01"
95
+ }
96
+
97
+ # puts PX.response(data).inspect
98
+
99
+ assert_equal "0000010059278491", PX.response(data)[:token]
100
+ end if ENV["TEST_INTEGRATION"]
101
+
102
+ test "pxpost build" do
103
+ data = {
104
+ amount: 8,
105
+ currency: "SGD",
106
+ type: PX::TYPE_AUTH,
107
+ txn_id: "txn1234",
108
+ ref: "ref1234",
109
+ token: "tok1234",
110
+ }
111
+
112
+ dict = XmlSimple.xml_in(PX::PxPost.build(data), forcearray: false)
113
+
114
+ assert_equal PX::PxPost::USERNAME, dict["PostUsername"]
115
+ assert_equal PX::PxPost::PASSWORD, dict["PostPassword"]
116
+ assert_equal "8.00", dict["Amount"]
117
+ assert_equal "SGD", dict["InputCurrency"]
118
+ assert_equal PX::TYPE_AUTH, dict["TxnType"]
119
+ assert_equal "txn1234", dict["TxnId"]
120
+ assert_equal "ref1234", dict["MerchantReference"]
121
+ assert_equal "tok1234", dict["DpsBillingId"]
122
+ end
123
+
124
+ test "pxpost POST" do
125
+ data = {
126
+ amount: 8,
127
+ currency: "USD",
128
+ type: PX::TYPE_PURCHASE,
129
+ token: "0000010059278491"
130
+ }
131
+
132
+ dict = PX.post(data)
133
+
134
+ assert_equal "1", dict["success"]
135
+ assert_equal "APPROVED", dict["responseText"]
136
+ assert_equal "1", dict["Authorized"]
137
+ assert_equal "8.00", dict["Amount"]
138
+ assert_equal "USD", dict["CurrencyName"]
139
+
140
+ end if ENV["TEST_INTEGRATION"]
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: px
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Cyril David
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: requests
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: xml-simple
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mote
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: hache
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cutest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ description: PaymentExpress 1.0 integration library
84
+ email:
85
+ - cyx@cyx.is
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE
91
+ - Makefile
92
+ - README.md
93
+ - lib/px.rb
94
+ - lib/xml/post.xml
95
+ - lib/xml/request.xml
96
+ - lib/xml/response.xml
97
+ - px.gemspec
98
+ - test/px_test.rb
99
+ homepage: http://cyx.is
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.2.2
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: PX library
123
+ test_files: []