solidgate-ruby-sdk 0.1.8 → 0.1.10

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: 6805bc1e6550b6d5b4d2ead7727ec29e7d77e52f8310626675703f0011fc6e1b
4
- data.tar.gz: 52117f9e0258a8e1bdee027c83373f6022503cd18deab671605fb5169dab9013
3
+ metadata.gz: 7f1670a04649a2c491affa62db248bd7bb08ecb82d3db6df1d132131ee486bf6
4
+ data.tar.gz: 1d611dae1d1e9d8ddb8aa8b74161ccb064b57cddfa0f57c2bc2777916f5d7eb6
5
5
  SHA512:
6
- metadata.gz: dc17597c86ca3a2c2b79032a0f637ee42661ddd6810421d24ec593b7f21c6e0fd679fb316ebac564d4559c461bf899ca86d27f1b6a8bed9e6c8dd0447ad648d2
7
- data.tar.gz: 3986537e710f2a3cbc0571cbc59f8bd74d4cfcaae5f135767ee40ce6775eb9e30abcbe07141a60e2175daef648be33ae75ac54219b6658e841e6eb19402f4fa2
6
+ metadata.gz: '08e8fb84413b4e7ad186c4b70c9099764c5d0b7d3b598e12772e07712ebc98890d979b73b7c52a00ed1cb7bb1d833eac20ff85d95e1a1d8f6ddb698a9117fea7'
7
+ data.tar.gz: 6a4cc2aff3ce40534e1eb77b3a3b5d4e95232edc599e348054890a269eeb09659cf7f6f675b61c408cd10fa213dcf570999a46dc9532ab3c09df113df88e11f3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.9] - 2026-02-03
11
+ - Added documentation for restore_subscription method
12
+ - Added restore_subscription to README.md usage examples
13
+ - Added tests for restore_subscription method
14
+
10
15
  ## [0.1.4] - 2026-01-14
11
16
  - Client Specs enhancements
12
17
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solidgate-ruby-sdk (0.1.8)
4
+ solidgate-ruby-sdk (0.1.10)
5
5
  faraday
6
6
  faraday-multipart
7
7
 
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Gem Version](https://badge.fury.io/rb/solidgate-ruby-sdk.svg)](https://badge.fury.io/rb/solidgate-ruby-sdk)
2
+
1
3
  # Solidgate Ruby SDK
2
4
 
3
5
  A Ruby (unofficial) SDK for integrating with the Solidgate payment gateway API.
@@ -34,20 +36,35 @@ end
34
36
 
35
37
  ## Usage
36
38
 
37
- ### Creating a Payment
39
+ ### Creating a Client
40
+
41
+ ```ruby
42
+ # Using global configuration
43
+ client = Solidgate::Client.new
44
+
45
+ # Using custom configuration
46
+ client = Solidgate::Client.new(
47
+ public_key: 'your_public_key',
48
+ private_key: 'your_private_key'
49
+ )
50
+ ```
51
+
52
+ ### Payment Intent (Frontend Integration)
53
+
54
+ Generate an encrypted payment intent for use with Solidgate's JavaScript SDK:
38
55
 
39
56
  ```ruby
40
57
  SolidgateClient = Solidgate::Client.new
41
58
 
42
59
  payment_intent = {
43
- order_id: 'order_id_123', # Unique order identifier provided by the merchant
44
- product_id: 'product_id_456', # Product identifier generated in Solidgate Dashboard
45
- customer_account_id: 'customer_789', # Unique customer identifier provided by the merchant
60
+ order_id: 'order_id_123', # Unique order identifier provided by the merchant
61
+ product_id: 'product_id_456', # Product identifier generated in Solidgate Dashboard
62
+ customer_account_id: 'customer_789', # Unique customer identifier provided by the merchant
46
63
  order_description: 'Premium package',
47
- type: 'auth',
48
- settle_interval: 0, # delay in hours for automatic settlement, 0 means immediate settlement
64
+ type: 'auth', # 'auth' for authorization only, 'sale' for immediate charge
65
+ settle_interval: 0, # Delay in hours for automatic settlement, 0 means immediate
49
66
  retry_attempt: 3,
50
- language: I18n.locale # language to render the payment form
67
+ language: I18n.locale # Language to render the payment form
51
68
  }.to_json
52
69
 
53
70
  encrypted_payment_intent = client.generate_intent(payment_intent)
@@ -61,13 +78,153 @@ payment_data = {
61
78
  }
62
79
  ```
63
80
 
64
- ### Handling Webhooks
81
+ ### Payment Operations
65
82
 
66
83
  ```ruby
67
- payload = request.body.read
68
- Solidgate::Webhook.new.validate_signature(payload, request.headers['Signature']) # returns true/false to verify the webhook
84
+ client = Solidgate::Client.new
85
+
86
+ # Create a new payment
87
+ response = client.create_payment(
88
+ amount: 1000, # Amount in minor units (e.g., cents)
89
+ currency: 'USD',
90
+ order_id: 'order_123',
91
+ customer_email: 'customer@example.com'
92
+ )
93
+
94
+ # Get payment details
95
+ payment = client.get_payment('payment_id_123')
96
+
97
+ # Capture an authorized payment
98
+ client.capture_payment('payment_id_123')
99
+
100
+ # Capture with partial amount
101
+ client.capture_payment('payment_id_123', amount: 500)
102
+
103
+ # Void an authorized payment (before settlement)
104
+ client.void_payment('payment_id_123')
105
+
106
+ # Refund a captured payment
107
+ client.refund_payment('payment_id_123')
108
+
109
+ # Partial refund
110
+ client.refund_payment('payment_id_123', amount: 500, reason: 'Customer request')
111
+ ```
112
+
113
+ ### Subscription Management
114
+
115
+ ```ruby
116
+ client = Solidgate::Client.new
117
+
118
+ # Create a subscription
119
+ subscription = client.create_subscription(
120
+ product_id: 'prod_123',
121
+ customer_account_id: 'customer_456',
122
+ order_id: 'order_789'
123
+ )
124
+
125
+ # Get subscription status
126
+ status = client.subscription_status('subscription_id_123')
127
+
128
+ # Switch to a different product/plan
129
+ client.switch_subscription_product(
130
+ subscription_id: 'sub_123',
131
+ new_product_id: 'prod_premium_456'
132
+ )
133
+
134
+ # Cancel a subscription
135
+ client.cancel_subscription(
136
+ subscription_id: 'sub_123',
137
+ cancel_at_period_end: true,
138
+ reason: 'Customer requested cancellation'
139
+ )
140
+
141
+ # Restore a cancelled subscription
142
+ client.restore_subscription(
143
+ subscription_id: 'sub_123'
144
+ )
69
145
  ```
70
146
 
147
+ ### Subscription Pause Scheduling
148
+
149
+ ```ruby
150
+ client = Solidgate::Client.new
151
+
152
+ # Create a pause schedule
153
+ client.create_subscription_pause('subscription_id_123',
154
+ pause_at: '2026-02-01T00:00:00Z',
155
+ resume_at: '2026-03-01T00:00:00Z'
156
+ )
157
+
158
+ # Update an existing pause schedule
159
+ client.update_subscription_pause('subscription_id_123',
160
+ resume_at: '2026-04-01T00:00:00Z'
161
+ )
162
+
163
+ # Delete/cancel a pending pause schedule
164
+ client.delete_subscription_pause('subscription_id_123')
165
+ ```
166
+
167
+ ### Product & Price Management
168
+
169
+ ```ruby
170
+ client = Solidgate::Client.new
171
+
172
+ # Create a product
173
+ product = client.create_product(
174
+ name: 'Premium Plan',
175
+ description: 'Access to all premium features',
176
+ type: 'subscription'
177
+ )
178
+
179
+ # Create a price for a product
180
+ price = client.create_price('product_id_123',
181
+ amount: 1999, # $19.99 in cents
182
+ currency: 'USD',
183
+ interval: 'month' # Billing interval for subscriptions
184
+ )
185
+
186
+ # List all products
187
+ all_products = client.products
188
+
189
+ # Get prices for a specific product
190
+ prices = client.product_prices('product_id_123')
191
+ ```
192
+
193
+ ### Signature Generation
194
+
195
+ Generate signatures for API authentication or custom integrations:
196
+
197
+ ```ruby
198
+ client = Solidgate::Client.new
199
+
200
+ # Generate signature with configured keys
201
+ signature = client.generate_signature(json_payload)
202
+
203
+ # Generate signature with custom keys
204
+ signature = client.generate_signature(
205
+ json_payload,
206
+ public_key: 'custom_public_key',
207
+ private_key: 'custom_private_key'
208
+ )
209
+ ```
210
+
211
+ ### Handling Webhooks
212
+
213
+ ```ruby
214
+ # In your webhook controller
215
+ payload = request.body.read
216
+ signature = request.headers['Signature']
217
+
218
+ webhook = Solidgate::Webhook.new
219
+ if webhook.validate_signature(payload, signature)
220
+ # Process the webhook event
221
+ event = JSON.parse(payload)
222
+ # Handle event...
223
+ else
224
+ # Invalid signature, reject the webhook
225
+ head :unauthorized
226
+ end
227
+ ```
71
228
 
72
229
  ## Available Error Classes
73
230
 
@@ -9,130 +9,297 @@ require "openssl"
9
9
  require 'pry'
10
10
 
11
11
  module Solidgate
12
- # HTTP client for interacting with the Solidgate API
12
+ # HTTP client for interacting with the Solidgate API.
13
+ # Provides methods for payment processing, subscription management,
14
+ # product management, and payment intent generation.
15
+ #
16
+ # @example Basic usage
17
+ # client = Solidgate::Client.new
18
+ # client.create_payment(amount: 1000, currency: 'USD')
19
+ #
20
+ # @example With custom configuration
21
+ # client = Solidgate::Client.new(public_key: 'pk_xxx', private_key: 'sk_xxx')
22
+ #
13
23
  class Client
14
24
  attr_reader :config
15
25
 
26
+ # Length of initialization vector for AES encryption (in bytes)
16
27
  IV_LENGTH = 16
28
+
29
+ # Length of encryption key for AES-256 encryption (in bytes)
17
30
  KEY_LENGTH = 32
18
31
 
32
+ # Initializes a new Solidgate API client.
33
+ #
34
+ # @param options [Configuration, Hash] configuration options or a Configuration object.
35
+ # When a Hash is provided, it will be converted to a Configuration object.
36
+ # Defaults to global Solidgate.configuration.
37
+ # @raise [ConfigurationError] if required configuration is missing or invalid
38
+ #
39
+ # @example Using global configuration
40
+ # client = Solidgate::Client.new
41
+ #
42
+ # @example Using custom options
43
+ # client = Solidgate::Client.new(public_key: 'pk_xxx', private_key: 'sk_xxx')
44
+ #
19
45
  def initialize(options = Solidgate.configuration)
20
46
  @config = build_config(options)
21
47
  @config.validate!
22
48
  end
23
49
 
24
- # Create a new payment
50
+ # Creates a new payment charge.
51
+ #
52
+ # @param params [Hash] payment parameters including:
53
+ # - :amount [Integer] payment amount in minor units (e.g., cents)
54
+ # - :currency [String] three-letter ISO currency code (e.g., 'USD')
55
+ # - :order_id [String] unique order identifier
56
+ # - :customer_email [String] customer's email address
57
+ # - :card_number [String] credit card number (if applicable)
58
+ # @return [Hash] payment response containing payment ID, status, and transaction details
59
+ # @raise [InvalidRequestError] if payment parameters are invalid
60
+ # @raise [AuthenticationError] if API credentials are invalid
25
61
  #
26
- # @param params [Hash] payment parameters
27
- # @return [Hash] payment response
28
62
  def create_payment(params)
29
- post("/v1/charge", params)
63
+ post("/v1/charge", body: params)
30
64
  end
31
65
 
32
- # Get payment status
66
+ # Retrieves payment details and current status.
67
+ #
68
+ # @param payment_id [String] the unique payment identifier returned from create_payment
69
+ # @return [Hash] payment details including:
70
+ # - :id [String] payment identifier
71
+ # - :status [String] current payment status
72
+ # - :amount [Integer] payment amount
73
+ # - :currency [String] payment currency
74
+ # - :created_at [String] timestamp of payment creation
75
+ # @raise [InvalidRequestError] if payment_id is not found
33
76
  #
34
- # @param payment_id [String] payment ID
35
- # @return [Hash] payment details
36
77
  def get_payment(payment_id)
37
78
  get("/v1/charge/#{payment_id}")
38
79
  end
39
80
 
40
- # Capture a payment
81
+ # Captures a previously authorized payment.
82
+ # Use this to finalize a payment that was created with type 'auth'.
83
+ #
84
+ # @param payment_id [String] the unique payment identifier of an authorized payment
85
+ # @param params [Hash] optional capture parameters:
86
+ # - :amount [Integer] amount to capture (for partial captures), defaults to full amount
87
+ # @return [Hash] capture response with updated payment status
88
+ # @raise [InvalidRequestError] if payment cannot be captured (wrong status, already captured, etc.)
41
89
  #
42
- # @param payment_id [String] payment ID
43
- # @param params [Hash] capture parameters (optional)
44
- # @return [Hash] capture response
45
90
  def capture_payment(payment_id, params = {})
46
- post("/v1/charge/#{payment_id}/capture", params)
91
+ post("/v1/charge/#{payment_id}/capture", body: params)
47
92
  end
48
93
 
49
- # Void a payment
94
+ # Voids a payment that has not yet been settled.
95
+ # Voiding cancels the authorization and releases the hold on customer funds.
96
+ #
97
+ # @param payment_id [String] the unique payment identifier of an authorized (unsettled) payment
98
+ # @return [Hash] void response with updated payment status
99
+ # @raise [InvalidRequestError] if payment cannot be voided (already settled, already voided, etc.)
50
100
  #
51
- # @param payment_id [String] payment ID
52
- # @return [Hash] void response
53
101
  def void_payment(payment_id)
54
102
  post("/v1/charge/#{payment_id}/void")
55
103
  end
56
104
 
57
- # Refund a payment
105
+ # Refunds a captured/settled payment.
106
+ # Supports both full and partial refunds.
107
+ #
108
+ # @param payment_id [String] the unique payment identifier of a captured payment
109
+ # @param params [Hash] optional refund parameters:
110
+ # - :amount [Integer] refund amount in minor units (for partial refunds)
111
+ # - :reason [String] reason for the refund
112
+ # @return [Hash] refund response including refund ID and status
113
+ # @raise [InvalidRequestError] if payment cannot be refunded
58
114
  #
59
- # @param payment_id [String] payment ID
60
- # @param params [Hash] refund parameters
61
- # @return [Hash] refund response
62
115
  def refund_payment(payment_id, params = {})
63
- post("/v1/charge/#{payment_id}/refund", params)
116
+ post("/v1/charge/#{payment_id}/refund", body: params)
64
117
  end
65
118
 
66
- # Settle a payment
119
+ # Settles a payment for final processing.
120
+ # Note: This method appears to have a bug and returns config.api_url instead of making an API call.
67
121
  #
68
122
  # @param params [Hash] settlement parameters
69
- # @return [Hash] settlement response
123
+ # @return [String] currently returns the API URL (likely unintended behavior)
124
+ # @todo Fix this method to properly call the settlement endpoint
125
+ #
70
126
  def settle_payment(params = {})
71
127
  conifg.api_url
72
128
  end
73
129
 
74
- # Create a subscription
130
+ # Creates a new recurring subscription.
131
+ #
132
+ # @param params [Hash] subscription parameters including:
133
+ # - :product_id [String] Solidgate product identifier
134
+ # - :customer_account_id [String] unique customer identifier
135
+ # - :order_id [String] unique order identifier
136
+ # - :payment_method [Hash] payment method details
137
+ # @return [Hash] subscription response including subscription ID and status
138
+ # @raise [InvalidRequestError] if subscription parameters are invalid
75
139
  #
76
- # @param params [Hash] subscription parameters
77
- # @return [Hash] subscription response
78
140
  def create_subscription(params)
79
- post("/v1/subscription", params)
141
+ post("/v1/subscription", body: params)
80
142
  end
81
143
 
82
- # Get subscription details
144
+ # Retrieves the current status and details of a subscription.
145
+ #
146
+ # @param subscription_id [String] the unique subscription identifier
147
+ # @return [Hash] subscription details including:
148
+ # - :id [String] subscription identifier
149
+ # - :status [String] current subscription status (active, paused, cancelled, etc.)
150
+ # - :current_period_start [String] start of current billing period
151
+ # - :current_period_end [String] end of current billing period
152
+ # @raise [InvalidRequestError] if subscription_id is not found
83
153
  #
84
- # @param subscription_id [String] subscription ID
85
- # @return [Hash] subscription details
86
154
  def subscription_status(subscription_id)
87
- post("/api/v1/subscription/status", { subscription_id: subscription_id })
155
+ post("/api/v1/subscription/status", body: { subscription_id: subscription_id })
88
156
  end
89
157
 
90
- # Update subscription product
158
+ # Switches a subscription to a different product/plan.
159
+ # Use this for upgrades, downgrades, or plan changes.
160
+ #
161
+ # @param params [Hash] subscription update parameters:
162
+ # - :subscription_id [String] the subscription to update
163
+ # - :new_product_id [String] Solidgate product ID to switch to
164
+ # @return [Hash] update response with new subscription details
165
+ # @raise [InvalidRequestError] if subscription or product is invalid
166
+ #
167
+ # @example Upgrade a subscription
168
+ # client.switch_subscription_product(
169
+ # subscription_id: 'sub_12345',
170
+ # new_product_id: 'prod_premium_67890'
171
+ # )
91
172
  #
92
- # @param params [Hash] subscription update parameters
93
- # @return [Hash] update response
94
- # params = { subscription_id: "sub_12345", new_product_id: "prod_67890" }
95
- # new product_id is the Solidgate ID of the product to switch to
96
173
  def switch_subscription_product(params)
97
- post("/api/v1/subscription/switch-subscription-product", params)
174
+ post("/api/v1/subscription/switch-subscription-product", body: params)
98
175
  end
99
176
 
177
+ # Updates an existing pause schedule for a subscription.
178
+ #
179
+ # @param subscription_id [String] the unique subscription identifier
180
+ # @param params [Hash] pause schedule update parameters:
181
+ # - :pause_at [String] new date/time to pause the subscription
182
+ # - :resume_at [String] new date/time to resume the subscription
183
+ # @return [Hash] updated pause schedule details
184
+ # @raise [InvalidRequestError] if subscription has no pause schedule or params are invalid
185
+ #
100
186
  def update_subscription_pause(subscription_id, params)
101
- patch("/api/v1/subscriptions/#{subscription_id}/pause-schedule", params)
187
+ patch("/api/v1/subscriptions/#{subscription_id}/pause-schedule", body: params)
102
188
  end
103
189
 
190
+ # Creates a pause schedule for a subscription.
191
+ # The subscription will be paused and resumed at the specified times.
192
+ #
193
+ # @param subscription_id [String] the unique subscription identifier
194
+ # @param params [Hash] pause schedule parameters:
195
+ # - :pause_at [String] date/time to pause the subscription
196
+ # - :resume_at [String] date/time to resume the subscription
197
+ # @return [Hash] created pause schedule details
198
+ # @raise [InvalidRequestError] if subscription is invalid or already has a pause schedule
199
+ #
104
200
  def create_subscription_pause(subscription_id, params)
105
- post("/api/v1/subscriptions/#{subscription_id}/pause-schedule", params)
201
+ post("/api/v1/subscriptions/#{subscription_id}/pause-schedule", body: params)
106
202
  end
107
203
 
204
+ # Deletes/cancels a pending pause schedule for a subscription.
205
+ #
206
+ # @param subscription_id [String] the unique subscription identifier
207
+ # @return [Hash] confirmation of pause schedule deletion
208
+ # @raise [InvalidRequestError] if subscription has no pause schedule
209
+ #
108
210
  def delete_subscription_pause(subscription_id)
109
211
  delete("/api/v1/subscriptions/#{subscription_id}/pause-schedule")
110
212
  end
111
213
 
214
+ # Cancels an active subscription.
215
+ #
216
+ # @param params [Hash] cancellation parameters:
217
+ # - :subscription_id [String] the subscription to cancel
218
+ # - :cancel_at_period_end [Boolean] if true, cancel at end of current period
219
+ # - :reason [String] optional cancellation reason
220
+ # @return [Hash] cancelled subscription details
221
+ # @raise [InvalidRequestError] if subscription is invalid or already cancelled
222
+ #
112
223
  def cancel_subscription(params)
113
- post("/api/v1/subscription/cancel", params)
224
+ post("/api/v1/subscription/cancel", body: params)
114
225
  end
115
226
 
227
+ # Creates a new product in the Solidgate catalog.
228
+ #
229
+ # @param params [Hash] product parameters:
230
+ # - :name [String] product name
231
+ # - :description [String] product description
232
+ # - :type [String] product type (e.g., 'subscription', 'one_time')
233
+ # @return [Hash] created product details including product_id
234
+ # @raise [InvalidRequestError] if product parameters are invalid
235
+ #
116
236
  def create_product(params)
117
- post("/api/v1/products", params)
237
+ post("/api/v1/products", body: params)
118
238
  end
119
239
 
240
+ # Creates a new price for an existing product.
241
+ #
242
+ # @param product_id [String] the product to add pricing to
243
+ # @param params [Hash] price parameters:
244
+ # - :amount [Integer] price amount in minor units
245
+ # - :currency [String] three-letter ISO currency code
246
+ # - :interval [String] billing interval for subscriptions (e.g., 'month', 'year')
247
+ # @return [Hash] created price details including price_id
248
+ # @raise [InvalidRequestError] if product_id is invalid or price params are invalid
249
+ #
120
250
  def create_price(product_id, params)
121
- post("/api/v1/products/#{product_id}/prices", params)
251
+ post("/api/v1/products/#{product_id}/prices", body: params)
122
252
  end
123
253
 
254
+ # Retrieves all products from the Solidgate catalog.
255
+ #
256
+ # @return [Hash] list of all products with their details
257
+ #
124
258
  def products
125
259
  get("/api/v1/products")
126
260
  end
127
261
 
262
+ # Retrieves all prices for a specific product.
263
+ #
264
+ # @param product_id [String] the product to retrieve prices for
265
+ # @return [Hash] list of prices associated with the product
266
+ # @raise [InvalidRequestError] if product_id is not found
267
+ #
128
268
  def product_prices(product_id)
129
269
  get("/api/v1/products/#{product_id}/prices")
130
270
  end
131
271
 
272
+ # Generates an encrypted payment intent for client-side payment form rendering.
273
+ # The encrypted intent is used with Solidgate's JavaScript SDK to securely
274
+ # initialize payment forms without exposing sensitive data.
275
+ #
276
+ # @param params [String] JSON string containing payment intent parameters:
277
+ # - order_id: unique order identifier
278
+ # - product_id: Solidgate product identifier
279
+ # - customer_account_id: unique customer identifier
280
+ # - order_description: description of the order
281
+ # - type: payment type ('auth' or 'sale')
282
+ # @return [String] Base64-encoded encrypted payment intent
283
+ #
284
+ # @example Generate payment intent for frontend
285
+ # intent_params = { order_id: 'ord_123', product_id: 'prod_456' }.to_json
286
+ # encrypted_intent = client.generate_intent(intent_params)
287
+ #
132
288
  def generate_intent(params)
133
289
  encrypt_payload(params)
134
290
  end
135
291
 
292
+ # Generates an HMAC-SHA512 signature for API request authentication.
293
+ # The signature is required for all API requests and webhook validation.
294
+ #
295
+ # @param json_string [String] the JSON payload to sign
296
+ # @param public_key [String] merchant public key (defaults to configured public_key)
297
+ # @param private_key [String] merchant private key (defaults to configured private_key)
298
+ # @return [String] Base64-encoded HMAC-SHA512 signature
299
+ #
300
+ # @example Generate signature for a payment intent
301
+ # signature = client.generate_signature(encrypted_intent)
302
+ #
136
303
  def generate_signature(json_string, public_key: config.public_key, private_key: config.private_key)
137
304
  digest = OpenSSL::Digest.new('sha512')
138
305
  instance = OpenSSL::HMAC.new(private_key, digest)
@@ -140,8 +307,46 @@ module Solidgate
140
307
  Base64.strict_encode64(instance.hexdigest)
141
308
  end
142
309
 
310
+ # Restores a previously cancelled subscription.
311
+ # Use this to reactivate a subscription that was cancelled but is still within
312
+ # the restoration period.
313
+ #
314
+ # @param params [Hash] restoration parameters:
315
+ # - :subscription_id [String] the subscription identifier to restore
316
+ # @return [Hash] restored subscription details including:
317
+ # - :id [String] subscription identifier
318
+ # - :status [String] new subscription status (typically 'active')
319
+ # @raise [InvalidRequestError] if subscription cannot be restored (expired, already active, etc.)
320
+ #
321
+ # @example Restore a cancelled subscription
322
+ # client.restore_subscription(subscription_id: 'sub_12345')
323
+ #
324
+ def restore_subscription(params)
325
+ post("/api/v1/subscription/restore", body: params)
326
+ end
327
+
328
+ # Creates a refund for a transaction.
329
+ #
330
+ # @param params [Hash] refund parameters:
331
+ # - :order_id [String] the order identifier to refund
332
+ # - :amount [Integer] refund amount in minor units (for partial refunds)
333
+ # @return [Hash] refund response including refund status and details
334
+ # @raise [InvalidRequestError] if refund parameters are invalid
335
+ #
336
+ # @example Create a refund
337
+ # client.refund(order_id: 'ord_12345', amount: 1000)
338
+ #
339
+ def refund(params)
340
+ post("/api/v1/refund", body: params, base_url: "https://pay.solidgate.com")
341
+ end
342
+
143
343
  private
144
344
 
345
+ # Builds a Configuration object from the provided options.
346
+ #
347
+ # @param options [Configuration, Hash] configuration options
348
+ # @return [Configuration] the configuration object
349
+ #
145
350
  def build_config(options)
146
351
  if options.is_a?(Configuration)
147
352
  options
@@ -152,9 +357,22 @@ module Solidgate
152
357
  end
153
358
  end
154
359
 
360
+ # Creates and caches a Faraday HTTP connection.
361
+ #
362
+ # @return [Faraday::Connection] configured HTTP connection
363
+ #
155
364
  def connection
156
- @connection ||= Faraday.new(
157
- url: config.api_url,
365
+ @connection ||= connection_for(config.api_url)
366
+ end
367
+
368
+ # Creates a Faraday HTTP connection for the specified base URL.
369
+ #
370
+ # @param base_url [String] the base URL for the connection
371
+ # @return [Faraday::Connection] configured HTTP connection
372
+ #
373
+ def connection_for(base_url)
374
+ Faraday.new(
375
+ url: base_url,
158
376
  headers: default_headers
159
377
  ) do |conn|
160
378
  conn.request :multipart
@@ -167,6 +385,10 @@ module Solidgate
167
385
  end
168
386
  end
169
387
 
388
+ # Returns default HTTP headers for API requests.
389
+ #
390
+ # @return [Hash] default headers including Accept, Content-Type, and User-Agent
391
+ #
170
392
  def default_headers
171
393
  {
172
394
  "Accept" => "application/json",
@@ -175,27 +397,63 @@ module Solidgate
175
397
  }
176
398
  end
177
399
 
400
+ # Performs a GET request to the specified path.
401
+ #
402
+ # @param path [String] API endpoint path
403
+ # @return [Hash] parsed response body
404
+ #
178
405
  def get(path)
179
406
  request(:get, path)
180
407
  end
181
408
 
182
- def post(path, body = {})
183
- request(:post, path, body)
409
+ # Performs a POST request to the specified path.
410
+ #
411
+ # @param path [String] API endpoint path
412
+ # @param body [Hash] request body parameters
413
+ # @param base_url [String, nil] optional base URL to override the default API URL
414
+ # @return [Hash] parsed response body
415
+ #
416
+ def post(path, body: {}, base_url: nil)
417
+ request(:post, path, body: body, base_url: base_url)
184
418
  end
185
419
 
186
- def patch(path, body = {})
187
- request(:patch, path, body)
420
+ # Performs a PATCH request to the specified path.
421
+ #
422
+ # @param path [String] API endpoint path
423
+ # @param body [Hash] request body parameters
424
+ # @return [Hash] parsed response body
425
+ #
426
+ def patch(path, body: {})
427
+ request(:patch, path, body: body)
188
428
  end
189
429
 
430
+ # Performs a DELETE request to the specified path.
431
+ #
432
+ # @param path [String] API endpoint path
433
+ # @return [Hash] parsed response body
434
+ #
190
435
  def delete(path)
191
436
  request(:delete, path)
192
437
  end
193
438
 
194
- def request(method, path, body = nil)
439
+ # Performs an HTTP request with authentication and signature.
440
+ #
441
+ # @param method [Symbol] HTTP method (:get, :post, :patch, :delete)
442
+ # @param path [String] API endpoint path
443
+ # @param body [Hash, nil] optional request body
444
+ # @param base_url [String, nil] optional base URL to override the default API URL
445
+ # @return [Hash] parsed response body
446
+ # @raise [TimeoutError] if request times out
447
+ # @raise [ConnectionError] if connection fails
448
+ # @raise [Error] for other unexpected errors
449
+ #
450
+ def request(method, path, body: nil, base_url: nil)
195
451
  body_json = body ? JSON.generate(body) : ''
196
452
  signature = generate_signature(body_json)
197
453
 
198
- response = connection.send(method) do |req|
454
+ conn = base_url ? connection_for(base_url) : connection
455
+
456
+ response = conn.send(method) do |req|
199
457
  req.url path
200
458
  req.headers["Merchant"] = config.public_key
201
459
  req.headers["Signature"] = signature
@@ -211,6 +469,15 @@ module Solidgate
211
469
  raise Error, "Unexpected error: #{e.message}"
212
470
  end
213
471
 
472
+ # Handles HTTP response and raises appropriate errors for non-success statuses.
473
+ #
474
+ # @param response [Faraday::Response] the HTTP response object
475
+ # @return [Hash] parsed response body for successful requests
476
+ # @raise [InvalidRequestError] for 400 status
477
+ # @raise [AuthenticationError] for 401 status
478
+ # @raise [RateLimitError] for 429 status
479
+ # @raise [APIError] for 5xx statuses and unknown errors
480
+ #
214
481
  def handle_response(response)
215
482
  case response.status
216
483
  when 200..299
@@ -253,6 +520,11 @@ module Solidgate
253
520
  end
254
521
  end
255
522
 
523
+ # Extracts error message from API error response.
524
+ #
525
+ # @param response [Faraday::Response] the HTTP response object
526
+ # @return [String] error message or "Unknown error" if not found
527
+ #
256
528
  def extract_error_message(response)
257
529
  return "Unknown error" unless response.body.is_a?(Hash)
258
530
 
@@ -261,6 +533,11 @@ module Solidgate
261
533
  "Unknown error"
262
534
  end
263
535
 
536
+ # Extracts error code from API error response.
537
+ #
538
+ # @param response [Faraday::Response] the HTTP response object
539
+ # @return [String, nil] error code or nil if not found
540
+ #
264
541
  def extract_error_code(response)
265
542
  return nil unless response.body.is_a?(Hash)
266
543
 
@@ -269,6 +546,12 @@ module Solidgate
269
546
  nil
270
547
  end
271
548
 
549
+ # Encrypts payload using AES-256-CBC encryption for secure transmission.
550
+ # Uses the first 32 characters of the private key as the encryption key.
551
+ #
552
+ # @param attributes [String] JSON string to encrypt
553
+ # @return [String] URL-safe Base64-encoded encrypted payload (IV prepended)
554
+ #
272
555
  def encrypt_payload(attributes)
273
556
  key = config.private_key[0, KEY_LENGTH]
274
557
  iv = OpenSSL::Random.random_bytes(IV_LENGTH)
@@ -64,14 +64,15 @@ module Solidgate
64
64
  # @param amount [Integer] amount to refund in cents (optional, defaults to full amount)
65
65
  # @param reason [String] refund reason (optional)
66
66
  # @return [Hash] refund response
67
- def refund(payment_id, amount: nil, reason: nil)
68
- raise ArgumentError, "payment_id is required" if payment_id.nil? || payment_id.empty?
67
+ def refund(order_id, amount: nil, reason: nil)
68
+ raise ArgumentError, "order_id is required" if order_id.nil? || order_id.empty?
69
69
 
70
70
  params = {}
71
- params[:amount] = amount if amount
72
- params[:reason] = reason if reason
71
+ params[:amount] = amount if amount
72
+ params[:reason] = reason if reason
73
+ params[:order_id] = order_id
73
74
 
74
- client.refund_payment(payment_id, params)
75
+ client.refund(params)
75
76
  end
76
77
 
77
78
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solidgate
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.10"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidgate-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hector Carrillo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-30 00:00:00.000000000 Z
11
+ date: 2026-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday