ipagos_ruby_sdk 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1362f01d2f6e9a8d6a44fb1c2dc216dfa29fe8ae34b433c87c51e6b75823cc54
4
- data.tar.gz: df9786fdd0806100f5ad19cc3df593e54a909056d9353cf8df23ac9b25d3b705
3
+ metadata.gz: cfd271bbab63e86184e5ac653608e8a548f988e626fae9d757bac28a4ae3bb46
4
+ data.tar.gz: 1707855b60cad9fe8f6125915ccea1249671f4ca495b356dc7fc2dca205319e6
5
5
  SHA512:
6
- metadata.gz: 512407d5a3b0a67f5d75899d57a8887181b3fd909bf4973ae0f531309032de12d2dd5d046f3c5cfcd7f54b94f80e121d015ed3c5bd5bd42143f6555905cf0fb0
7
- data.tar.gz: 3ad4c4111f5f49372857509d3a3a8d838b2bb6b92776c9633983a84bd1b037c2a6d78a53a85f070ab8d00cb5485d2361d024d509f36fd8e99415f995d1fa85c5
6
+ metadata.gz: 470403a065245ee9f318b539e6863fecc02fa9e2b2c4aae52ffc64f16c349cdd8027a7b2049e0a1db01ee63644f09a4209cbaa46a17b9c246ed2c038df81bae1
7
+ data.tar.gz: 74b29ec550bbe45c5b1868f0555e16fb7cbe72ec39eb14570f9a5866b4513f1b0a8a23a5c6aa683e390b8b68479e743291bf2c9bfa1f6a158e6a1cb8dcb40d14
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+ require "base64"
6
+ require "openssl"
7
+ require "time"
8
+ require "uri"
9
+
10
+ module IpagosRubySdk
11
+ class Client
12
+ SANDBOX_URL = "https://apitest.cybersource.com"
13
+ PRODUCTION_URL = "https://api.cybersource.com"
14
+
15
+ attr_reader :config
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ @base_url = config.environment == :production ? PRODUCTION_URL : SANDBOX_URL
20
+
21
+ @conn = Faraday.new(url: @base_url) do |f|
22
+ f.headers["Content-Type"] = "application/json"
23
+ f.headers["v-c-merchant-id"] = config.merchant_id
24
+ f.adapter Faraday.default_adapter
25
+ end
26
+ end
27
+
28
+ def create_session(origin)
29
+ path = "/microform/v2/sessions"
30
+ payload = {
31
+ targetOrigins: [origin.chomp("/")],
32
+ allowedCardNetworks: ["VISA", "MASTERCARD", "AMEX"],
33
+ allowedPaymentTypes: ["CARD"],
34
+ clientVersion: "v2"
35
+ }
36
+ send_request(:post, path, payload)
37
+ end
38
+
39
+ def create_payment(amount:, bill_to:, token: nil, card_data: nil, reference: nil, line_items: nil)
40
+ path = "/pts/v2/payments"
41
+
42
+ items = if line_items.nil? || line_items.empty?
43
+ [{
44
+ unitPrice: amount.total,
45
+ quantity: 1,
46
+ productName: "Servicio General",
47
+ productSku: "GEN-001"
48
+ }]
49
+ else
50
+ line_items.map(&:to_h)
51
+ end
52
+
53
+ payload = {
54
+ clientReferenceInformation: { code: reference || "ORD-#{Time.now.to_i}" },
55
+ processingInformation: { capture: true },
56
+ orderInformation: {
57
+ amountDetails: amount.to_h,
58
+ billTo: bill_to.to_h,
59
+ lineItems: items
60
+ }
61
+ }
62
+
63
+ if token
64
+ payload[:tokenInformation] = { transientTokenJwt: token }
65
+ elsif card_data
66
+ payload[:paymentInformation] = {
67
+ card: {
68
+ number: card_data[:number],
69
+ expirationMonth: card_data[:expiration_month],
70
+ expirationYear: card_data[:expiration_year],
71
+ cvv: card_data[:cvv]
72
+ }
73
+ }
74
+ else
75
+ raise ArgumentError, "Se requiere un 'token' o 'card_data' para procesar el pago"
76
+ end
77
+
78
+ send_request(:post, path, payload)
79
+ end
80
+
81
+ def void_payment(id)
82
+ path = "/pts/v2/payments/#{id}/voids"
83
+ payload = { clientReferenceInformation: { code: "void_#{Time.now.to_i}" } }
84
+ send_request(:post, path, payload)
85
+ end
86
+
87
+ # Reembolsar un pago (Refund) - Versión Corregida para CyberSource
88
+ def refund_payment(id, amount)
89
+ path = "/pts/v2/payments/#{id}/refunds"
90
+
91
+ payload = {
92
+ clientReferenceInformation: { code: "refund_#{Time.now.to_i}" },
93
+ orderInformation: {
94
+ amountDetails: {
95
+ totalAmount: amount.total,
96
+ currency: amount.currency
97
+ }
98
+ }
99
+ }
100
+ send_request(:post, path, payload)
101
+ end
102
+
103
+ def get_payment(id)
104
+ send_request(:get, "/pts/v2/payments/#{id}")
105
+ end
106
+
107
+ def get_payment_status(id)
108
+ get_payment(id)
109
+ end
110
+
111
+ private
112
+
113
+ def send_request(method, path, payload = nil)
114
+ headers = generate_auth_headers(method, path, payload)
115
+ body = (payload && method != :get) ? payload.to_json : nil
116
+ response = @conn.run_request(method, path, body, headers)
117
+ handle_response(response)
118
+ rescue JSON::ParserError
119
+ ::IpagosRubySdk::Response.error("Respuesta de API inválida (JSON Malformed)", 500)
120
+ rescue StandardError => e
121
+ ::IpagosRubySdk::Response.new(success: false, error_message: "Excepción de Red: #{e.message}", status_code: 500)
122
+ end
123
+
124
+ def generate_auth_headers(method, path, payload)
125
+ date = Time.now.httpdate
126
+ digest = ""
127
+
128
+ if payload && method != :get
129
+ json_body = payload.to_json
130
+ hash = OpenSSL::Digest::SHA256.digest(json_body)
131
+ digest = "SHA-256=#{Base64.strict_encode64(hash)}"
132
+ end
133
+
134
+ signature_headers = [
135
+ "(request-target): #{method.to_s.downcase} #{path}",
136
+ "host: #{URI.parse(@base_url).host}",
137
+ "date: #{date}"
138
+ ]
139
+ signature_headers << "digest: #{digest}" unless digest.empty?
140
+ signature_headers << "v-c-merchant-id: #{@config.merchant_id}"
141
+
142
+ sig_string = signature_headers.join("\n")
143
+ decoded_secret = Base64.decode64(@config.secret_key)
144
+ hmac = OpenSSL::HMAC.digest("sha256", decoded_secret, sig_string)
145
+
146
+ header_list = "(request-target) host date#{' digest' unless digest.empty?} v-c-merchant-id"
147
+
148
+ headers = {
149
+ "Date" => date,
150
+ "Signature" => "keyid=\"#{@config.key_id}\", algorithm=\"HmacSHA256\", headers=\"#{header_list}\", signature=\"#{Base64.strict_encode64(hmac)}\"",
151
+ "v-c-merchant-id" => @config.merchant_id
152
+ }
153
+ headers["Digest"] = digest unless digest.empty?
154
+ headers
155
+ end
156
+
157
+ def handle_response(response)
158
+ success = response.status.between?(200, 299)
159
+ raw_body = response.body.to_s.strip
160
+
161
+ data = begin
162
+ if raw_body.start_with?("{", "[")
163
+ JSON.parse(raw_body)
164
+ elsif raw_body.start_with?("eyJ")
165
+ { "token" => raw_body }
166
+ else
167
+ { "raw_response" => raw_body }
168
+ end
169
+ rescue JSON::ParserError
170
+ { "raw_response" => raw_body }
171
+ end
172
+
173
+ ::IpagosRubySdk::Response.new(
174
+ success: success,
175
+ data: data,
176
+ error_message: success ? nil : "Error #{response.status}: #{data['message'] || 'Respuesta inválida'}",
177
+ status_code: response.status
178
+ )
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpagosRubySdk
4
+ module Environment
5
+ SANDBOX = :sandbox
6
+ PRODUCTION = :production
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module IpagosRubySdk
2
+ class AmountDetails < BaseModel
3
+ attr_accessor :total, :currency
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ module IpagosRubySdk
5
+ class BaseModel
6
+ def initialize(attributes = {})
7
+ attributes.each do |key, value|
8
+ send("#{key}=", value) if respond_to?("#{key}=")
9
+ end
10
+ end
11
+
12
+ # Este método equivale a tu toArray() de PHP
13
+ def to_h
14
+ instance_variables.each_with_object({}) do |var, hash|
15
+ value = instance_variable_get(var)
16
+ # Limpiamos el '@' del nombre de la variable
17
+ key = var.to_s.delete("@")
18
+
19
+ # Si el valor es otro modelo, lo convertimos recursivamente
20
+ hash[key] = value.respond_to?(:to_h) ? value.to_h : value
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module IpagosRubySdk
2
+ class BillTo < BaseModel
3
+ attr_accessor :first_name, :last_name, :address1, :address2,
4
+ :locality, :administrative_area, :postal_code,
5
+ :country, :district, :email, :phone_number
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpagosRubySdk
4
+ class LineItem < BaseModel
5
+ attr_accessor :unit_price, :quantity, :product_name, :product_sku
6
+
7
+ def to_h
8
+ {
9
+ unitPrice: unit_price,
10
+ quantity: quantity,
11
+ productName: product_name,
12
+ productSku: product_sku
13
+ }.compact
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpagosRubySdk
4
+ # Configuración (Equivale a IpagosConfig.php)
5
+ # Usamos una estructura con bloque para incluir la validación de presencia
6
+ Configuration = Struct.new(
7
+ :environment,
8
+ :merchant_id,
9
+ :key_id,
10
+ :secret_key,
11
+ keyword_init: true
12
+ ) do
13
+ def initialize(*)
14
+ super
15
+ raise ArgumentError, "merchant_id es requerido" if merchant_id.nil?
16
+ raise ArgumentError, "key_id es requerido" if key_id.nil?
17
+ raise ArgumentError, "secret_key es requerido" if secret_key.nil?
18
+ end
19
+ end
20
+
21
+ # Respuesta (Equivale a IpagosResponse.php)
22
+ class Response
23
+ attr_reader :success, :data, :error_message, :status_code
24
+
25
+ def initialize(success:, data: nil, error_message: nil, status_code: 200)
26
+ @success = success
27
+ @data = data
28
+ @error_message = error_message
29
+ @status_code = status_code
30
+ # Hacemos el objeto inmutable
31
+ freeze
32
+ end
33
+
34
+ def success?
35
+ @success
36
+ end
37
+
38
+ # Obtiene valores del payload soportando Strings o Símbolos
39
+ def get(key, default = nil)
40
+ return default unless @data.is_a?(Hash)
41
+
42
+ @data[key.to_s] || @data[key.to_sym] || default
43
+ end
44
+
45
+ # Factory para respuestas exitosas
46
+ def self.success(data = nil, status_code = 200)
47
+ new(success: true, data: data, status_code: status_code)
48
+ end
49
+
50
+ # Factory para respuestas de error
51
+ def self.error(message, status_code = 400)
52
+ new(success: false, error_message: message, status_code: status_code)
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IpagosRubySdk
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ipagos_ruby_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Integraciones iPagos
@@ -81,6 +81,13 @@ files:
81
81
  - README.md
82
82
  - Rakefile
83
83
  - lib/ipagos_ruby_sdk.rb
84
+ - lib/ipagos_ruby_sdk/client.rb
85
+ - lib/ipagos_ruby_sdk/environment.rb
86
+ - lib/ipagos_ruby_sdk/models/amount_details.rb
87
+ - lib/ipagos_ruby_sdk/models/base_model.rb
88
+ - lib/ipagos_ruby_sdk/models/bill_to.rb
89
+ - lib/ipagos_ruby_sdk/models/line_item.rb
90
+ - lib/ipagos_ruby_sdk/types.rb
84
91
  - lib/ipagos_ruby_sdk/version.rb
85
92
  - sig/ipagos_ruby_sdk.rbs
86
93
  homepage: https://gitlab.com/ipagos/ipagos-ruby-sdk