sepay_pg_ruby 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 980e4390afecf243f51eb965074c00e391d868dbdd349fec1c046beef294e5e6
4
+ data.tar.gz: e4599d686032db231c65e6b693dca349df62097555f702c8601bb4281a56e0a2
5
+ SHA512:
6
+ metadata.gz: 3c70415760129e44bc87be0dfdfd2cebc0b7ac4435d57715e095c4b6c7f977c187c18109c2a4947f3e76a711b47cf3279ebde3177c94eced357874415e14e4d0
7
+ data.tar.gz: 5313fd157ac12fb915009e47089a2af820cb5cf6b5ac1ab9a92574583e36ef197b01539803299a9c69a640bc92f50ca71bff0c10027f820f543dd0c3b9fe9be1
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # sepay_pg_ruby
2
+
3
+ Ruby SDK cho SePay Payment Gateway, dùng cho Rails project.
4
+
5
+ ## Cài đặt trong Rails
6
+
7
+ Trong `Gemfile`:
8
+
9
+ ```ruby
10
+ gem 'sepay_pg_ruby', path: 'gem/sepay_pg_ruby'
11
+ ```
12
+
13
+ Sau đó chạy:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ ## Khởi tạo client
20
+
21
+ ```ruby
22
+ client = SepayPgRuby::Client.new(
23
+ env: Rails.env.production? ? 'production' : 'sandbox',
24
+ merchant_id: ENV.fetch('SEPAY_MERCHANT_ID'),
25
+ secret_key: ENV.fetch('SEPAY_SECRET_KEY')
26
+ )
27
+ ```
28
+
29
+ ## Orders API
30
+
31
+ ```ruby
32
+ # Danh sách orders
33
+ client.order.all(per_page: 20, q: 'ORDER-001')
34
+
35
+ # Chi tiết order
36
+ client.order.retrieve('ORDER-001')
37
+
38
+ # Void transaction
39
+ client.order.void_transaction('ORDER-001')
40
+
41
+ # Cancel order
42
+ client.order.cancel('ORDER-001')
43
+ ```
44
+
45
+ ## Checkout API
46
+
47
+ ```ruby
48
+ checkout_url = client.checkout.init_checkout_url
49
+
50
+ fields = client.checkout.init_one_time_payment_fields(
51
+ order_invoice_number: 'ORDER-001',
52
+ order_amount: 100_000,
53
+ currency: 'VND',
54
+ order_description: 'Thanh toan don hang ORDER-001',
55
+ payment_method: 'BANK_TRANSFER',
56
+ success_url: 'https://your-app.com/payment/success',
57
+ error_url: 'https://your-app.com/payment/error',
58
+ cancel_url: 'https://your-app.com/payment/cancel'
59
+ )
60
+ ```
61
+
62
+ ## Verify IPN signature
63
+
64
+ ```ruby
65
+ is_valid = SepayPgRuby::IPN.verify_signature(params.to_unsafe_h, ENV['SEPAY_SECRET_KEY'])
66
+ ```
@@ -0,0 +1,59 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module SepayPgRuby
5
+ class Checkout
6
+ SIGNABLE_FIELDS = [
7
+ 'merchant',
8
+ 'operation',
9
+ 'payment_method',
10
+ 'order_amount',
11
+ 'currency',
12
+ 'order_invoice_number',
13
+ 'order_description',
14
+ 'customer_id',
15
+ 'success_url',
16
+ 'error_url',
17
+ 'cancel_url',
18
+ 'order_id',
19
+ 'agreement_id',
20
+ 'agreement_name',
21
+ 'agreement_type',
22
+ 'agreement_payment_frequency',
23
+ 'agreement_amount_per_payment'
24
+ ].freeze
25
+
26
+ def initialize(client)
27
+ @client = client
28
+ end
29
+
30
+ def init_checkout_url
31
+ "#{@client.base_checkout_url}/init"
32
+ end
33
+
34
+ def init_one_time_payment_fields(fields)
35
+ payload = fields.transform_keys(&:to_s).dup
36
+ payload['merchant'] = @client.merchant_id
37
+ payload['operation'] ||= 'PURCHASE'
38
+ payload['signature'] = sign_fields(payload)
39
+ payload
40
+ end
41
+
42
+ def sign_fields(fields)
43
+ normalized = fields.transform_keys(&:to_s)
44
+ signed = []
45
+
46
+ # We must iterate over the keys in the order they were added to the hash
47
+ # to match the insertion-order behavior of the Node.js SDK
48
+ normalized.keys.each do |field|
49
+ next unless SIGNABLE_FIELDS.include?(field)
50
+ next if normalized[field].nil?
51
+
52
+ signed << "#{field}=#{normalized[field]}"
53
+ end
54
+
55
+ digest = OpenSSL::HMAC.digest('sha256', @client.secret_key, signed.join(','))
56
+ Base64.strict_encode64(digest)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,98 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require_relative 'errors'
6
+
7
+ module SepayPgRuby
8
+ class Client
9
+ attr_reader :env, :merchant_id, :secret_key, :api_version, :checkout_version, :order, :checkout
10
+
11
+ def initialize(env:, merchant_id:, secret_key:, api_version: 'v1', checkout_version: 'v1')
12
+ @env = env
13
+ @merchant_id = merchant_id
14
+ @secret_key = secret_key
15
+ @api_version = api_version
16
+ @checkout_version = checkout_version
17
+
18
+ validate_config!
19
+
20
+ @order = Order.new(self)
21
+ @checkout = Checkout.new(self)
22
+ end
23
+
24
+ def base_api_url
25
+ if env == 'sandbox'
26
+ "https://pgapi-sandbox.sepay.vn/#{api_version}"
27
+ else
28
+ "https://pgapi.sepay.vn/#{api_version}"
29
+ end
30
+ end
31
+
32
+ def base_checkout_url
33
+ if env == 'sandbox'
34
+ "https://pay-sandbox.sepay.vn/#{checkout_version}/checkout"
35
+ else
36
+ "https://pay.sepay.vn/#{checkout_version}/checkout"
37
+ end
38
+ end
39
+
40
+ def request(method:, endpoint:, params: nil, body: nil)
41
+ uri = URI.parse("#{base_api_url}/#{endpoint}")
42
+ uri.query = URI.encode_www_form(params) if params.present?
43
+
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true
46
+
47
+ req = build_request(method, uri)
48
+ req['Content-Type'] = 'application/json'
49
+ req['Authorization'] = "Basic #{Base64.strict_encode64("#{merchant_id}:#{secret_key}")}"
50
+ req.body = JSON.generate(body) if body
51
+
52
+ res = http.request(req)
53
+ parsed = parse_response_body(res.body)
54
+
55
+ raise RequestError, "SePay request failed (#{res.code}): #{parsed.inspect}" if res.code.to_i >= 400
56
+
57
+ { status: res.code.to_i, headers: res.to_hash, body: parsed }
58
+ end
59
+
60
+ private
61
+
62
+ def build_request(method, uri)
63
+ case method.to_s.upcase
64
+ when 'GET'
65
+ Net::HTTP::Get.new(uri)
66
+ when 'POST'
67
+ Net::HTTP::Post.new(uri)
68
+ when 'PUT'
69
+ Net::HTTP::Put.new(uri)
70
+ when 'DELETE'
71
+ Net::HTTP::Delete.new(uri)
72
+ else
73
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
74
+ end
75
+ end
76
+
77
+ def parse_response_body(body)
78
+ return nil if body.nil? || body.strip.empty?
79
+
80
+ JSON.parse(body)
81
+ rescue JSON::ParserError
82
+ body
83
+ end
84
+
85
+ def validate_config!
86
+ unless ['sandbox', 'production'].include?(env)
87
+ raise ConfigurationError, "Invalid env '#{env}'. Use 'sandbox' or 'production'."
88
+ end
89
+
90
+ if merchant_id.to_s.strip.empty? || secret_key.to_s.strip.empty?
91
+ raise ConfigurationError, 'merchant_id and secret_key are required'
92
+ end
93
+
94
+ raise ConfigurationError, 'Only v1 is supported for api_version' unless api_version == 'v1'
95
+ raise ConfigurationError, 'Only v1 is supported for checkout_version' unless checkout_version == 'v1'
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module SepayPgRuby
2
+ class Error < StandardError; end
3
+ class ConfigurationError < Error; end
4
+ class RequestError < Error; end
5
+ end
@@ -0,0 +1,31 @@
1
+ require 'openssl'
2
+
3
+ module SepayPgRuby
4
+ module IPN
5
+ module_function
6
+
7
+ def verify_signature(payload, secret_key)
8
+ payload_hash = payload.transform_keys(&:to_s)
9
+ received_signature = payload_hash['signature']
10
+ return false if received_signature.nil? || received_signature.empty?
11
+
12
+ data_to_verify = payload_hash.reject { |k, _| k == 'signature' }
13
+ sorted_keys = data_to_verify.keys.select { |k| !data_to_verify[k].nil? }.sort
14
+ query_string = sorted_keys.map { |key| "#{key}=#{data_to_verify[key]}" }.join('&')
15
+
16
+ calculated = OpenSSL::HMAC.hexdigest('sha256', secret_key.to_s, query_string)
17
+ secure_compare(received_signature, calculated)
18
+ rescue StandardError
19
+ false
20
+ end
21
+
22
+ def secure_compare(a, b)
23
+ return false unless a.is_a?(String) && b.is_a?(String)
24
+ return false unless a.bytesize == b.bytesize
25
+
26
+ result = 0
27
+ a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
28
+ result.zero?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module SepayPgRuby
2
+ class Order
3
+ def initialize(client)
4
+ @client = client
5
+ end
6
+
7
+ def all(query_params = {})
8
+ @client.request(method: 'GET', endpoint: 'order', params: query_params)
9
+ end
10
+
11
+ def retrieve(order_invoice_number)
12
+ @client.request(method: 'GET', endpoint: "order/detail/#{order_invoice_number}")
13
+ end
14
+
15
+ def void_transaction(order_invoice_number)
16
+ @client.request(
17
+ method: 'POST',
18
+ endpoint: 'order/voidTransaction',
19
+ body: { order_invoice_number: order_invoice_number }
20
+ )
21
+ end
22
+
23
+ def cancel(order_invoice_number)
24
+ @client.request(
25
+ method: 'POST',
26
+ endpoint: 'order/cancel',
27
+ body: { order_invoice_number: order_invoice_number }
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module SepayPgRuby
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'sepay_pg_ruby/version'
2
+ require_relative 'sepay_pg_ruby/errors'
3
+ require_relative 'sepay_pg_ruby/client'
4
+ require_relative 'sepay_pg_ruby/order'
5
+ require_relative 'sepay_pg_ruby/checkout'
6
+ require_relative 'sepay_pg_ruby/ipn'
7
+
8
+ module SepayPgRuby
9
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'lib/sepay_pg_ruby/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'sepay_pg_ruby'
5
+ spec.version = SepayPgRuby::VERSION
6
+ spec.authors = ['Hiun']
7
+ spec.email = ['hieufix1710@gmail.com']
8
+
9
+ spec.summary = 'Ruby SDK for SePay Payment Gateway'
10
+ spec.description = 'Ruby SDK for SePay payment gateway with order and checkout support.'
11
+ spec.homepage = 'https://sepay.vn'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = '>= 3.0.0'
14
+
15
+ spec.files = Dir.chdir(__dir__) do
16
+ Dir['lib/**/*.rb'] + ['README.md', 'sepay_pg_ruby.gemspec']
17
+ end
18
+
19
+ spec.require_paths = ['lib']
20
+ spec.metadata['rubygems_mfa_required'] = 'true'
21
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sepay_pg_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hiun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby SDK for SePay payment gateway with order and checkout support.
14
+ email:
15
+ - hieufix1710@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/sepay_pg_ruby.rb
22
+ - lib/sepay_pg_ruby/checkout.rb
23
+ - lib/sepay_pg_ruby/client.rb
24
+ - lib/sepay_pg_ruby/errors.rb
25
+ - lib/sepay_pg_ruby/ipn.rb
26
+ - lib/sepay_pg_ruby/order.rb
27
+ - lib/sepay_pg_ruby/version.rb
28
+ - sepay_pg_ruby.gemspec
29
+ homepage: https://sepay.vn
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ rubygems_mfa_required: 'true'
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 3.0.0
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.4.10
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Ruby SDK for SePay Payment Gateway
53
+ test_files: []