offsite_payments_latipay 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ecf683c3c4de93947bb41644d46081b0a9bf637f78262140b1af9f0abacd4259
4
+ data.tar.gz: 53e6339b948cf9476a49fb2c15f6b6f459260a8f86776d950048829095683f33
5
+ SHA512:
6
+ metadata.gz: 67d8fbe0faef544cb5a0c6d214e6bc78ecdc956feaf102c9680e9f3505a822f51f9cb9566cae509075c14e7c05a1ee28407764dd261eb65ad946adc01c137478
7
+ data.tar.gz: 636ee0f0d2cfb1f20b25d332f50546f20960d1eedc5ff859c0664ca2cfe6e8245333c9eee62065b3435793d9fa6201c471b42e6a58c937c11b3d67e7e8375f3f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 SeaLink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # Offsite Payments
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/offsite_payments_latipay.svg)](http://badge.fury.io/rb/offsite_payments_latipay)
4
+ [![Build Status](https://github.com/sealink/offsite_payments_latipay/workflows/Build%20and%20Test/badge.svg?branch=master)](https://github.com/sealink/offsite_payments_latipay/actions)
5
+
6
+
7
+ Offsite Payments is an extraction from the ecommerce system [Shopify](http://www.shopify.com). Shopify's requirements for a simple and unified API to handle dozens of different offsite payment pages (often called hosted payment pages) with very different exposed APIs was the chief principle in designing the library.
8
+
9
+ It was developed for usage in Ruby on Rails web applications and integrates seamlessly
10
+ as a Rails plugin. It should also work as a stand alone Ruby library, but much of the benefit is in the ActionView helpers which are Rails-specific.
11
+
12
+ This gem provides integration for [Latipay Payments](https://www.latipay.net/)
13
+ through the Offsite Payments gem.
14
+
15
+ ## Installation
16
+
17
+ ### From Git
18
+
19
+ You can check out the latest source from git:
20
+
21
+ git clone https://github.com/sealink/offsite_payments_latipay.git
22
+
23
+ ### From RubyGems
24
+
25
+ Installation from RubyGems:
26
+
27
+ gem install offsite_payments_latipay
28
+
29
+ Or, if you're using Bundler, just add the following to your Gemfile:
30
+
31
+ gem 'offsite_payments_latipay'
32
+
33
+ ## Misc.
34
+
35
+ - This library is MIT licensed.
36
+
37
+ ## Release
38
+
39
+ To publish a new version of this gem the following steps must be taken.
40
+
41
+ - Update the version in the following files
42
+ ```
43
+ CHANGELOG.md
44
+ lib/offsite_payments_latipay/version.rb
45
+ ```
46
+ - Create a tag using the format v0.1.0
47
+ - Follow build progress in GitHub actions
@@ -0,0 +1,247 @@
1
+ module OffsitePayments
2
+ module Integrations
3
+ module Latipay
4
+
5
+ def self.notification(post, options = {})
6
+ Notification.new(post, options)
7
+ end
8
+
9
+ def self.return(query_string, options = {})
10
+ Return.new(query_string, options)
11
+ end
12
+
13
+ class Interface
14
+ include ActiveUtils::PostsData # ssl_get/post
15
+
16
+ def self.base_url
17
+ "https://api.latipay.net/v2"
18
+ end
19
+
20
+ def initialize(api_key, user_id)
21
+ @api_key = api_key
22
+ @user_id = user_id
23
+ end
24
+
25
+ def sign(fields)
26
+ message = fields.compact.sort.map{ |k,v| "#{k.to_s}=#{v}" }.join('&').concat(@api_key)
27
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @api_key, message)
28
+ end
29
+
30
+ def verify_signature(message, signature)
31
+ signature == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @api_key, message)
32
+ end
33
+
34
+ private
35
+
36
+ def standard_headers
37
+ {
38
+ 'Content-Type' => 'application/json'
39
+ }
40
+ end
41
+
42
+ def parse_response(raw_response)
43
+ JSON.parse(raw_response)
44
+ end
45
+
46
+ class RequestError < StandardError
47
+ attr_reader :exception, :message, :response_code
48
+
49
+ def initialize(response)
50
+ @response = response
51
+ @response_code = @response['code']
52
+ @success = @response_code == '0'
53
+ @message = @response['message']
54
+ end
55
+
56
+ # Latipay doesn't provide error codes for each interface, this is the only list for all the errors.
57
+ ERRORS = {
58
+ '201' => 'order not exist',
59
+ '204' => 'Some fields from input are null',
60
+ '205' => 'Cannot find out corresponding key for the user code or user is disabled or user is not activity',
61
+ '206' => 'Signature from Merchant request is wrong',
62
+ '207' => 'Param is wrong',
63
+ '300' => 'Gateway not exist',
64
+ '301' => 'Wrong account info or currency info',
65
+ '302' => 'Paycompany not exist',
66
+ '303' => 'Wrong pay type from merchant',
67
+ '304' => 'Wallet does not support this payment method',
68
+ '305' => 'No margin plan for the merchant',
69
+ '450' => 'Wallet not assign to user',
70
+ '1' => 'FAIL'
71
+ }
72
+
73
+ def success?
74
+ !!@success
75
+ end
76
+
77
+ def errors
78
+ fail NotImplementedError, "This method must be implemented on the subclass"
79
+ end
80
+
81
+ def error_code_text
82
+ errors[@response_code]
83
+ end
84
+ end
85
+ end
86
+
87
+ class TransactionInterface < Interface
88
+ def self.url
89
+ "#{base_url}/transaction"
90
+ end
91
+
92
+ def call(options)
93
+ options[:signature] = self.sign(options)
94
+ raw_response = ssl_post(self.class.url, options.to_json, standard_headers)
95
+ parsed_response = parse_response(raw_response)
96
+ validate_response(parsed_response)
97
+ "#{parsed_response['host_url']}/#{parsed_response['nonce']}"
98
+ end
99
+
100
+ def validate_response(parsed_response)
101
+ raise TransactionRequestError, parsed_response unless parsed_response['code'] == 0
102
+ message = parsed_response['nonce'] + parsed_response['host_url']
103
+ signature = parsed_response['signature']
104
+ raise StandardError, 'Invalid Signature in response' unless verify_signature(message, signature)
105
+ end
106
+
107
+ class TransactionRequestError < RequestError
108
+ def errors
109
+ ERRORS
110
+ end
111
+ end
112
+ end
113
+
114
+ class QueryInterface < Interface
115
+ def self.url(merchant_reference)
116
+ "#{base_url}/transaction/#{CGI.escape(merchant_reference)}"
117
+ end
118
+
119
+ def call(merchant_reference)
120
+ options = { user_id: @user_id }
121
+ signature = self.sign(options.merge({ merchant_reference: merchant_reference }))
122
+ options[:signature] = signature
123
+
124
+ raise ArgumentError, "Merchant reference must be specified" if merchant_reference.blank?
125
+ url = "#{self.class.url(merchant_reference)}?#{options.to_query}"
126
+ raw_response = ssl_get(url, standard_headers)
127
+ parsed_response = parse_response(raw_response)
128
+ validate_response(parsed_response)
129
+ parsed_response
130
+ end
131
+
132
+ def validate_response(parsed_response)
133
+ raise QueryRequestError, parsed_response unless parsed_response['code'] == 0
134
+ message = "#{parsed_response['merchant_reference']}#{parsed_response['payment_method']}#{parsed_response['status']}#{parsed_response['currency']}#{parsed_response['amount']}"
135
+ signature = parsed_response['signature']
136
+ raise StandardError, 'Invalid Signature in response' unless verify_signature(message, signature)
137
+ end
138
+
139
+ class QueryRequestError < RequestError
140
+ def errors
141
+ ERRORS
142
+ end
143
+ end
144
+ end
145
+
146
+ class RefundInterface < Interface
147
+ def self.url
148
+ # according to latipay doc, this url does not have a version part.
149
+ "https://api.latipay.net/refund"
150
+ end
151
+
152
+ def call(order_id, refund_amount, reference = '')
153
+ raise ArgumentError, "Order ID must be specified" if order_id.blank?
154
+ raise ArgumentError, "Refund amount must be specified" if refund_amount.blank?
155
+ options = { refund_amount: refund_amount, reference: reference, user_id: @user_id, order_id: order_id }
156
+ options[:signature] = self.sign(options)
157
+ raw_response = ssl_post(self.class.url, options.to_json, standard_headers)
158
+ parsed_response = parse_response(raw_response)
159
+ validate_response(parsed_response)
160
+ parsed_response['message']
161
+ end
162
+
163
+ def validate_response(parsed_response)
164
+ raise RefundRequestError, parsed_response unless parsed_response['code'] == 0
165
+ end
166
+
167
+ class RefundRequestError < RequestError
168
+ def errors
169
+ ERRORS
170
+ end
171
+ end
172
+ end
173
+
174
+ class Helper < OffsitePayments::Helper
175
+ def initialize(order, credentials, options = {})
176
+ @api_key = credentials.fetch(:api_key)
177
+ @user_id = credentials.fetch(:user_id)
178
+ super(order, credentials.fetch(:user_id), options.except(
179
+ :payment_method, :ip, :product_name
180
+ ))
181
+
182
+ add_field 'version', '2.0'
183
+ add_field 'payment_method', options.fetch(:payment_method)
184
+ add_field 'ip', options.fetch(:ip)
185
+ add_field 'product_name', options.fetch(:product_name)
186
+ add_field 'callback_url', options.fetch(:callback_url) { options.fetch(:return_url) }
187
+ add_field 'wallet_id', credentials.fetch(:wallet_id)
188
+ add_field 'amount', options.fetch(:amount)
189
+ add_field 'return_url', options.fetch(:return_url)
190
+ end
191
+
192
+ mapping :order, 'merchant_reference'
193
+ mapping :account, 'user_id'
194
+
195
+
196
+ def transaction_url
197
+ if form_fields['payment_method'] == 'wechat'
198
+ form_fields.merge!({ 'present_qr' => '1' })
199
+ end
200
+ TransactionInterface.new(@api_key, @user_id).call(form_fields)
201
+ end
202
+ end
203
+
204
+ class Notification < OffsitePayments::Notification
205
+ def initialize(params, credentials = {})
206
+ token = params.fetch('Token') { params.fetch('token') }
207
+ @params = QueryInterface.new(credentials.fetch(:api_key), credentials.fetch(:user_id)).call(token)
208
+ end
209
+
210
+ def complete?
211
+ params['status'] == 'paid'
212
+ end
213
+
214
+ def transaction_id
215
+ params['order_id']
216
+ end
217
+
218
+ # the money amount we received in X.2 decimal.
219
+ def gross
220
+ params['amount']
221
+ end
222
+
223
+ def status
224
+ params['status']
225
+ end
226
+
227
+ # Acknowledge the transaction to Latipay. This method has to be called after a new
228
+ # apc arrives. Latipay will verify that all the information we received are correct and will return a
229
+ # ok or a fail.
230
+ #
231
+ # Example:
232
+ #
233
+ # def ipn
234
+ # notify = LatipayNotification.new(request.raw_post)
235
+ #
236
+ # if notify.acknowledge
237
+ # ... process order ... if notify.complete?
238
+ # else
239
+ # ... log possible hacking attempt ...
240
+ # end
241
+ def acknowledge(authcode = nil)
242
+ true
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,7 @@
1
+ module OffsitePayments
2
+ module Integration
3
+ module Latipay
4
+ VERSION = '0.2.0'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ class OffsitePaymentsLatipay
2
+ require 'offsite_payments'
3
+ require_relative 'offsite_payments/integrations/latipay'
4
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: offsite_payments_latipay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Zheng Jing
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: offsite_payments
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: money
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: actionpack
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: actionview
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: test-unit
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: This gem extends the activemerchant offsite_payments gem providing integration
154
+ of Latipay.
155
+ email: zheng.jing@sealink.com.au
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - LICENSE
161
+ - README.md
162
+ - lib/offsite_payments/integrations/latipay.rb
163
+ - lib/offsite_payments/version.rb
164
+ - lib/offsite_payments_latipay.rb
165
+ homepage: https://github.com/sealink/offsite_payments_latipay
166
+ licenses:
167
+ - MIT
168
+ metadata: {}
169
+ post_install_message:
170
+ rdoc_options: []
171
+ require_paths:
172
+ - lib
173
+ required_ruby_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ required_rubygems_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ requirements: []
184
+ rubygems_version: 3.2.3
185
+ signing_key:
186
+ specification_version: 4
187
+ summary: Latipay integration for the activemerchant offsite_payments gem.
188
+ test_files: []