px 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.
@@ -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: []