pxfusion 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ # PxFusion
2
+
3
+ [![Build Status](https://magnum.travis-ci.com/3months/pxfusion.png?token=FA4EnSunh4Af329WCePs&branch=master)](https://magnum.travis-ci.com/3months/pxfusion)
4
+
5
+ A Rubygem for talking to DPS's PxFusion payment product via their SOAP API. Includes the ablity to start a transaction, and then query for the status of that transaction once the payment is complete, following the pictured flow:
6
+
7
+ ![PxFusion Workflow](http://www.paymentexpress.com/DPS/media/technical/Work_Flow.png)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'pxfusion'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install pxfusion
22
+
23
+ Some configuration settings are available:
24
+
25
+ ``` ruby
26
+ # Your PxFusion username
27
+ PxFusion.username = 'sample'
28
+
29
+ # Your PxFusion password
30
+ PxFusion.password = 'sample'
31
+
32
+ # A global return URL
33
+ # You can also override the return URL by passing
34
+ # it into a Transaction constructor
35
+ PxFusion.default_return_url = 'https://test.site/purchase'
36
+
37
+ # Override the default currency to charge in
38
+ # (Default is NZD)
39
+ PxFusion.default_currency = 'USD'
40
+ ```
41
+
42
+ In a Rails project, you should put this configuration in an initializer or your Rails
43
+ environment configuration. Remember, DO NOT PLACE PASSWORDS IN YOUR CODE. Instead, you
44
+ might want to load your configuration into environment variables, and refer to them
45
+ using notation like `ENV['PXFUSION_USERNAME']`.
46
+
47
+ ## Usage
48
+
49
+ The gem wraps around PxFusion's SOAP API. Given correct credentials, it will happily build a `PxFusion` transaction for you, but then it's up to you to build the form required to submit to DPS. Any `PxFusion::Transaction` object can be passed straight to a `form_for` method though, so it oughtn't be too difficult:
50
+
51
+ ``` ruby
52
+ # app/controllers/payments_controller.rb
53
+
54
+ def new
55
+ @order = Order.find(1)
56
+ @transaction = PxFusion::Transaction.new(amount: @order.total, reference: @order.to_param, return_url: payments_path)
57
+ render
58
+ end
59
+
60
+ # This is the POST-back destination for PxFusion
61
+ def create
62
+ @order = Order.find(1)
63
+ @transaction = PxFusion::Transaction.fetch(params[:sessionid])
64
+
65
+ if @transaction.status == PxFusion.statuses[:approved]
66
+ @order.paid!
67
+ respond_to root_path and return
68
+ else
69
+ render :new
70
+ end
71
+ end
72
+ ```
73
+
74
+ And then your view:
75
+
76
+ ``` erb
77
+ # app/views/payments/new.html.erb
78
+
79
+ <%= form_for @transaction do |f| %>
80
+ <%= f.hidden_field :session_id, name: 'SessionId' %>
81
+ <%= text_field_tag 'CardNumber' %>
82
+ <%= date_select_tag 'ExpiryMonth', include_days: false %>
83
+ <%= text_field_tag 'CardHolderName' %>
84
+ <%= text_field_tag 'Cvc2', maxlength: 4 %>
85
+ <%= f.submit 'Make Payment' %>
86
+ <% end %>
87
+ ```
88
+
89
+ **Please read the [PxFusion docs](http://www.paymentexpress.com/Technical_Resources/Ecommerce_NonHosted/PxFusion) while implementing your payment flow to ensure you understand the process - it'll help you avoid confusing problems, which is important, as this gateway is not well documented**.
90
+
91
+
92
+
93
+ ## Contributing
94
+
95
+ 1. Fork it
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. Run the specs: `rspec spec`
98
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
99
+ 4. Push to the branch (`git push origin my-new-feature`)
100
+ 5. Create new Pull Request
101
+
102
+ ## Think something's missing?
103
+
104
+ If you can think of a way that this gem could be easier to use, or if you've found a bug (reasonably) likely, then please [lodge an issue on Github](https://github.com/3months/pxfusion/issues/), so that we can help out. Thanks!
105
+
106
+
107
+ ## If you need to regenerate the HTTP fixtures
108
+
109
+ For portability and testing speed, we use fixtures to test API calls against, rather than calling the actual API. This works usually, but if you're seeing strange results or have changed any API methods, you can rengenerate the fixtures:
110
+
111
+ 1. Remove the current fixtures: `rm -r spec/fixtures`
112
+ 3. Add your credentials to `spec/spec_helper.rb` **BE SURE NOT TO COMMIT THIS FILE**
113
+ 4. Generate a Transaction and add the transaction ID into: `spec/pxfusion/transaction_spec.rb`
114
+ 5. Run the specs
115
+ 6. Commit and pull request the fixture changes as above if necessary
116
+
117
+
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
@@ -0,0 +1,59 @@
1
+ require "pxfusion/version"
2
+ require "pxfusion/client"
3
+ require "pxfusion/transaction"
4
+
5
+ module PxFusion
6
+ class << self
7
+ attr_writer :endpoint,
8
+ :form_endpoint,
9
+ :username,
10
+ :password,
11
+ :default_currency,
12
+ :logger,
13
+ :logging
14
+ attr_accessor :default_return_url
15
+
16
+ [:username, :password].each do |required_attribute|
17
+ define_method required_attribute do
18
+ raise "#{required_attribute} must be set" if !instance_variable_get("@#{required_attribute}")
19
+ instance_variable_get("@#{required_attribute}")
20
+ end
21
+ end
22
+
23
+ def endpoint
24
+ @endpoint ||= "https://sec.paymentexpress.com/pxf/pxf.svc"
25
+ end
26
+
27
+ def form_endpoint
28
+ @form_endpoint ||= "https://sec.paymentexpress.com/pxmi3/pxfusionauth"
29
+ end
30
+
31
+ def default_currency
32
+ @default_currency ||= "NZD"
33
+ end
34
+
35
+ def logger
36
+ @logger ||= Logger.new(STDOUT) if logging
37
+ end
38
+
39
+ def logging
40
+ @logging ||= false
41
+ end
42
+
43
+ def client
44
+ @client ||= Client.new
45
+ end
46
+
47
+ def statuses
48
+ {
49
+ approved: 0,
50
+ declined: 1,
51
+ retry: 2,
52
+ invalid_post: 3,
53
+ unknown: 4,
54
+ cancelled: 5,
55
+ not_found: 6
56
+ }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ require "savon"
2
+
3
+ class PxFusion::Client < Savon::Client
4
+ def initialize(options = {})
5
+ super(options.merge(
6
+ wsdl: PxFusion.endpoint.dup + "?wsdl",
7
+ element_form_default: :qualified,
8
+ log: PxFusion.logging,
9
+ logger: PxFusion.logger,
10
+ filters: [:password]
11
+ ))
12
+ end
13
+ end
@@ -0,0 +1,115 @@
1
+ require "ostruct"
2
+ require "active_support/core_ext/hash/reverse_merge"
3
+
4
+ class PxFusion::Transaction < OpenStruct
5
+ def initialize(attributes = {})
6
+ attributes.reverse_merge!(
7
+ username: PxFusion.username,
8
+ password: PxFusion.password,
9
+ currency: PxFusion.default_currency,
10
+ return_url: "https://test.host/",
11
+ type: 'Purchase',
12
+ )
13
+
14
+ super(attributes)
15
+ [:username, :password, :currency, :amount, :type].each do |required_attribute|
16
+ raise ArgumentError.new("Missing attribute: #{required_attribute}") if !self.send(required_attribute)
17
+ end
18
+
19
+ {amount: 12, currency: 3, reference: 16, return_url: 255, type: 8}.each_pair do |attribute, length|
20
+ next unless self.send(attribute)
21
+ attribute_value = self.send(attribute).to_s
22
+ given_length = attribute_value.length
23
+ raise ArgumentError.new("PxFusion #{attribute} too long (max #{length} characters). #{attribute} given (#{given_length} characters): #{attribute_value}") if given_length > length
24
+ end
25
+ end
26
+
27
+ def generate_session_id!
28
+ response = PxFusion.client.call(
29
+ :get_transaction_id,
30
+ message: Request.get_transaction_id(self)
31
+ ).body[:get_transaction_id_response][:get_transaction_id_result]
32
+
33
+ self.id = response[:transaction_id]
34
+ self.session_id = response[:session_id]
35
+
36
+ raise "Session could not be obtained from DPS" unless id && session_id
37
+
38
+ session_id
39
+ end
40
+
41
+ def self.fetch(id)
42
+ response = PxFusion.client.call(:get_transaction,message: {username: PxFusion.username, password: PxFusion.password, transactionId: id}).body
43
+ attributes = response[:get_transaction_response][:get_transaction_result]
44
+ raise PxFusion::Transaction::NotFound if attributes[:status].to_i == PxFusion.statuses[:not_found]
45
+
46
+ mapped_attributes = attributes.dup
47
+ attributes.each do |attribute, value|
48
+ case attribute
49
+ when :currency_name
50
+ mapped_attributes[:currency] = attributes[:currency_name]
51
+ when :txn_type
52
+ mapped_attributes[:type] = attributes[:txn_type]
53
+ when :response_text
54
+ mapped_attributes[:response] = attributes[:response_text]
55
+ when :merchant_reference
56
+ mapped_attributes[:reference] = attributes[:merchant_reference]
57
+ when :status
58
+ mapped_attributes[:status] = attributes[:status].to_i
59
+ end
60
+ end
61
+
62
+ self.new(mapped_attributes.merge(username: PxFusion.username, password: PxFusion.password))
63
+ end
64
+
65
+ private
66
+
67
+
68
+ class NotFound < Exception
69
+ end
70
+
71
+ module Request
72
+ def self.get_transaction_id_with_token(transaction)
73
+ attributes = transaction.instance_variable_get("@table")
74
+
75
+ msg = {
76
+ username: attributes[:username],
77
+ password: attributes[:password],
78
+ tranDetail: {
79
+ amount: attributes[:amount],
80
+ currency: attributes[:currency],
81
+ enableAddBillCard: true,
82
+ merchantReference: attributes[:reference],
83
+ returnUrl: attributes[:return_url],
84
+ txnRef: attributes[:reference],
85
+ txnType: attributes[:type]
86
+ }
87
+ }
88
+ end
89
+
90
+ def self.get_transaction_id_without_token(transaction)
91
+ attributes = transaction.instance_variable_get("@table")
92
+
93
+ msg = {
94
+ username: attributes[:username],
95
+ password: attributes[:password],
96
+ tranDetail: {
97
+ amount: attributes[:amount],
98
+ currency: attributes[:currency],
99
+ merchantReference: attributes[:reference],
100
+ returnUrl: attributes[:return_url],
101
+ txnRef: attributes[:reference],
102
+ txnType: attributes[:type]
103
+ }
104
+ }
105
+ end
106
+
107
+ def self.get_transaction_id(transaction)
108
+ # Build the hash to be sent to DPS
109
+ # THE ORDER MATTERS
110
+ attributes = transaction.instance_variable_get("@table")
111
+
112
+ attributes[:token_billing] ? get_transaction_id_with_token(transaction) : get_transaction_id_without_token(transaction)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,3 @@
1
+ module PxFusion
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pxfusion/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pxfusion"
8
+ spec.version = PxFusion::VERSION
9
+ spec.authors = ["Josh McArthur"]
10
+ spec.email = ["joshua.mcarthur@gmail.com"]
11
+ spec.description = %q{A Rubygem for talking to DPS's PxFusion payment product}
12
+ spec.summary = %q{A Rubygem for talking to DPS's PxFusion payment product}
13
+ spec.homepage = "https://github.com/3months/pxfusion"
14
+ spec.license = "GPL"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "vcr"
25
+ spec.add_development_dependency "webmock"
26
+ spec.add_dependency "savon"
27
+ spec.add_dependency "activesupport"
28
+ end
@@ -0,0 +1,50 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://sec.paymentexpress.com/PxF/pxf.svc
6
+ body:
7
+ encoding: UTF-8
8
+ string: <?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
9
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://paymentexpress.com"
10
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><tns:GetTransaction><tns:username><USERNAME></tns:username><tns:password><PASSWORD></tns:password><tns:transactionId>1234</tns:transactionId></tns:GetTransaction></env:Body></env:Envelope>
11
+ headers:
12
+ Soapaction:
13
+ - '"http://paymentexpress.com/IPxFusion/GetTransaction"'
14
+ Content-Type:
15
+ - text/xml;charset=UTF-8
16
+ Content-Length:
17
+ - '501'
18
+ Accept-Encoding:
19
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
20
+ Accept:
21
+ - '*/*'
22
+ User-Agent:
23
+ - Ruby
24
+ response:
25
+ status:
26
+ code: 200
27
+ message: OK
28
+ headers:
29
+ Server:
30
+ - DPS_PX_SERVER
31
+ Cache-Control:
32
+ - private
33
+ Content-Type:
34
+ - text/xml; charset=utf-8
35
+ Vary:
36
+ - Accept-Encoding
37
+ Date:
38
+ - Thu, 08 Aug 2013 02:03:37 GMT
39
+ Content-Length:
40
+ - '561'
41
+ body:
42
+ encoding: UTF-8
43
+ string: <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetTransactionResponse
44
+ xmlns="http://paymentexpress.com"><GetTransactionResult xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><amount/><authCode/><billingId/><cardHolderName/><cardName/><cardNumber/><cardNumber2/><currencyId/><currencyName/><currencyRate
45
+ i:nil="true"/><cvc2ResultCode/><dateExpiry/><dateSettlement>0001-01-01T00:00:00</dateSettlement><dpsBillingId/><dpsTxnRef/><merchantReference/><responseCode/><responseText/><riskRuleMatches
46
+ xmlns:a="http://schemas.datacontract.org/2004/07/"/><sessionId>1234</sessionId><status>6</status><testMode>false</testMode><transactionId>1234</transactionId><transactionResultFields
47
+ xmlns:a="http://schemas.datacontract.org/2004/07/"/><txnData1/><txnData2/><txnData3/><txnMac/><txnRef/><txnType/></GetTransactionResult></GetTransactionResponse></s:Body></s:Envelope>
48
+ http_version:
49
+ recorded_at: Thu, 08 Aug 2013 02:03:39 GMT
50
+ recorded_with: VCR 2.5.0
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://sec.paymentexpress.com/PxF/pxf.svc
6
+ body:
7
+ encoding: UTF-8
8
+ string: <?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
9
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://paymentexpress.com"
10
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><tns:GetTransaction><tns:username><USERNAME></tns:username><tns:password><PASSWORD></tns:password><tns:transactionId>0000010016327641e4c03879868bd001</tns:transactionId></tns:GetTransaction></env:Body></env:Envelope>
11
+ headers:
12
+ Soapaction:
13
+ - '"http://paymentexpress.com/IPxFusion/GetTransaction"'
14
+ Content-Type:
15
+ - text/xml;charset=UTF-8
16
+ Content-Length:
17
+ - '529'
18
+ Accept-Encoding:
19
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
20
+ Accept:
21
+ - '*/*'
22
+ User-Agent:
23
+ - Ruby
24
+ response:
25
+ status:
26
+ code: 200
27
+ message: OK
28
+ headers:
29
+ Server:
30
+ - DPS_PX_SERVER
31
+ Cache-Control:
32
+ - private
33
+ Content-Type:
34
+ - text/xml; charset=utf-8
35
+ Vary:
36
+ - Accept-Encoding
37
+ Date:
38
+ - Thu, 08 Aug 2013 02:03:38 GMT
39
+ Content-Length:
40
+ - '769'
41
+ body:
42
+ encoding: UTF-8
43
+ string: <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetTransactionResponse
44
+ xmlns="http://paymentexpress.com"><GetTransactionResult xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><amount>1.00</amount><authCode>145217</authCode><billingId/><cardHolderName>JOE
45
+ BLOGGS</cardHolderName><cardName>Visa</cardName><cardNumber>411111........11</cardNumber><cardNumber2/><currencyId>554</currencyId><currencyName>NZD</currencyName><currencyRate
46
+ i:nil="true"/><cvc2ResultCode>NotUsed</cvc2ResultCode><dateExpiry>1212</dateExpiry><dateSettlement>2013-08-01T00:00:00</dateSettlement><dpsBillingId/><dpsTxnRef>0000000108d32712</dpsTxnRef><merchantReference>Px
47
+ Fusion - PHP</merchantReference><responseCode>00</responseCode><responseText>APPROVED</responseText><riskRuleMatches
48
+ xmlns:a="http://schemas.datacontract.org/2004/07/"/><sessionId>0000010016327641e4c03879868bd001</sessionId><status>0</status><testMode>false</testMode><transactionId>0000010016327641e4c03879868bd001</transactionId><transactionResultFields
49
+ xmlns:a="http://schemas.datacontract.org/2004/07/"/><txnData1/><txnData2/><txnData3/><txnMac>2BC20210</txnMac><txnRef>51f9cd5705064611</txnRef><txnType>Purchase</txnType></GetTransactionResult></GetTransactionResponse></s:Body></s:Envelope>
50
+ http_version:
51
+ recorded_at: Thu, 08 Aug 2013 02:03:40 GMT
52
+ recorded_with: VCR 2.5.0