Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
ruby-version: '2.7'
bundler-cache: true

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
solidgate-ruby-sdk (0.1.9)
solidgate-ruby-sdk (0.1.10)
faraday
faraday-multipart

Expand Down
68 changes: 48 additions & 20 deletions lib/solidgate/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def initialize(options = Solidgate.configuration)
# @raise [AuthenticationError] if API credentials are invalid
#
def create_payment(params)
post("/v1/charge", params)
post("/v1/charge", body: params)
end

# Retrieves payment details and current status.
Expand Down Expand Up @@ -88,7 +88,7 @@ def get_payment(payment_id)
# @raise [InvalidRequestError] if payment cannot be captured (wrong status, already captured, etc.)
#
def capture_payment(payment_id, params = {})
post("/v1/charge/#{payment_id}/capture", params)
post("/v1/charge/#{payment_id}/capture", body: params)
end

# Voids a payment that has not yet been settled.
Expand All @@ -113,7 +113,7 @@ def void_payment(payment_id)
# @raise [InvalidRequestError] if payment cannot be refunded
#
def refund_payment(payment_id, params = {})
post("/v1/charge/#{payment_id}/refund", params)
post("/v1/charge/#{payment_id}/refund", body: params)
end

# Settles a payment for final processing.
Expand All @@ -138,7 +138,7 @@ def settle_payment(params = {})
# @raise [InvalidRequestError] if subscription parameters are invalid
#
def create_subscription(params)
post("/v1/subscription", params)
post("/v1/subscription", body: params)
end

# Retrieves the current status and details of a subscription.
Expand All @@ -152,7 +152,7 @@ def create_subscription(params)
# @raise [InvalidRequestError] if subscription_id is not found
#
def subscription_status(subscription_id)
post("/api/v1/subscription/status", { subscription_id: subscription_id })
post("/api/v1/subscription/status", body: { subscription_id: subscription_id })
end

# Switches a subscription to a different product/plan.
Expand All @@ -171,7 +171,7 @@ def subscription_status(subscription_id)
# )
#
def switch_subscription_product(params)
post("/api/v1/subscription/switch-subscription-product", params)
post("/api/v1/subscription/switch-subscription-product", body: params)
end

# Updates an existing pause schedule for a subscription.
Expand All @@ -184,7 +184,7 @@ def switch_subscription_product(params)
# @raise [InvalidRequestError] if subscription has no pause schedule or params are invalid
#
def update_subscription_pause(subscription_id, params)
patch("/api/v1/subscriptions/#{subscription_id}/pause-schedule", params)
patch("/api/v1/subscriptions/#{subscription_id}/pause-schedule", body: params)
end

# Creates a pause schedule for a subscription.
Expand All @@ -198,7 +198,7 @@ def update_subscription_pause(subscription_id, params)
# @raise [InvalidRequestError] if subscription is invalid or already has a pause schedule
#
def create_subscription_pause(subscription_id, params)
post("/api/v1/subscriptions/#{subscription_id}/pause-schedule", params)
post("/api/v1/subscriptions/#{subscription_id}/pause-schedule", body: params)
end

# Deletes/cancels a pending pause schedule for a subscription.
Expand All @@ -221,7 +221,7 @@ def delete_subscription_pause(subscription_id)
# @raise [InvalidRequestError] if subscription is invalid or already cancelled
#
def cancel_subscription(params)
post("/api/v1/subscription/cancel", params)
post("/api/v1/subscription/cancel", body: params)
end

# Creates a new product in the Solidgate catalog.
Expand All @@ -234,7 +234,7 @@ def cancel_subscription(params)
# @raise [InvalidRequestError] if product parameters are invalid
#
def create_product(params)
post("/api/v1/products", params)
post("/api/v1/products", body: params)
end

# Creates a new price for an existing product.
Expand All @@ -248,7 +248,7 @@ def create_product(params)
# @raise [InvalidRequestError] if product_id is invalid or price params are invalid
#
def create_price(product_id, params)
post("/api/v1/products/#{product_id}/prices", params)
post("/api/v1/products/#{product_id}/prices", body: params)
end

# Retrieves all products from the Solidgate catalog.
Expand Down Expand Up @@ -322,7 +322,22 @@ def generate_signature(json_string, public_key: config.public_key, private_key:
# client.restore_subscription(subscription_id: 'sub_12345')
#
def restore_subscription(params)
post("/api/v1/subscription/restore", params)
post("/api/v1/subscription/restore", body: params)
end

# Creates a refund for a transaction.
#
# @param params [Hash] refund parameters:
# - :order_id [String] the order identifier to refund
# - :amount [Integer] refund amount in minor units (for partial refunds)
# @return [Hash] refund response including refund status and details
# @raise [InvalidRequestError] if refund parameters are invalid
#
# @example Create a refund
# client.refund(order_id: 'ord_12345', amount: 1000)
#
def refund(params)
post("/api/v1/refund", body: params, base_url: "https://pay.solidgate.com")
end

private
Expand All @@ -347,8 +362,17 @@ def build_config(options)
# @return [Faraday::Connection] configured HTTP connection
#
def connection
@connection ||= Faraday.new(
url: config.api_url,
@connection ||= connection_for(config.api_url)
end

# Creates a Faraday HTTP connection for the specified base URL.
#
# @param base_url [String] the base URL for the connection
# @return [Faraday::Connection] configured HTTP connection
#
def connection_for(base_url)
Faraday.new(
url: base_url,
headers: default_headers
) do |conn|
conn.request :multipart
Expand Down Expand Up @@ -386,10 +410,11 @@ def get(path)
#
# @param path [String] API endpoint path
# @param body [Hash] request body parameters
# @param base_url [String, nil] optional base URL to override the default API URL
# @return [Hash] parsed response body
#
def post(path, body = {})
request(:post, path, body)
def post(path, body: {}, base_url: nil)
request(:post, path, body: body, base_url: base_url)
end

# Performs a PATCH request to the specified path.
Expand All @@ -398,8 +423,8 @@ def post(path, body = {})
# @param body [Hash] request body parameters
# @return [Hash] parsed response body
#
def patch(path, body = {})
request(:patch, path, body)
def patch(path, body: {})
request(:patch, path, body: body)
end

# Performs a DELETE request to the specified path.
Expand All @@ -416,16 +441,19 @@ def delete(path)
# @param method [Symbol] HTTP method (:get, :post, :patch, :delete)
# @param path [String] API endpoint path
# @param body [Hash, nil] optional request body
# @param base_url [String, nil] optional base URL to override the default API URL
# @return [Hash] parsed response body
# @raise [TimeoutError] if request times out
# @raise [ConnectionError] if connection fails
# @raise [Error] for other unexpected errors
#
def request(method, path, body = nil)
def request(method, path, body: nil, base_url: nil)
body_json = body ? JSON.generate(body) : ''
signature = generate_signature(body_json)

response = connection.send(method) do |req|
conn = base_url ? connection_for(base_url) : connection

response = conn.send(method) do |req|
req.url path
req.headers["Merchant"] = config.public_key
req.headers["Signature"] = signature
Expand Down
11 changes: 6 additions & 5 deletions lib/solidgate/payment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ def void(payment_id)
# @param amount [Integer] amount to refund in cents (optional, defaults to full amount)
# @param reason [String] refund reason (optional)
# @return [Hash] refund response
def refund(payment_id, amount: nil, reason: nil)
raise ArgumentError, "payment_id is required" if payment_id.nil? || payment_id.empty?
def refund(order_id, amount: nil, reason: nil)
raise ArgumentError, "order_id is required" if order_id.nil? || order_id.empty?

params = {}
params[:amount] = amount if amount
params[:reason] = reason if reason
params[:amount] = amount if amount
params[:reason] = reason if reason
params[:order_id] = order_id

client.refund_payment(payment_id, params)
client.refund(params)
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/solidgate/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Solidgate
VERSION = "0.1.9"
VERSION = "0.1.10"
end
93 changes: 93 additions & 0 deletions spec/solidgate/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,99 @@
end
end

describe "#refund" do
let(:refund_params) do
{
order_id: "order_123",
amount: 1000
}
end

before do
stub_request(:post, /pay\.solidgate\.com/).to_return(
status: 200,
body: success_response.to_json,
headers: { "Content-Type" => "application/json" }
)
end

it "sends POST request to https://pay.solidgate.com/api/v1/refund" do
client.refund(refund_params)

expect(WebMock).to have_requested(:post, "https://pay.solidgate.com/api/v1/refund")
.with(body: refund_params.to_json)
end

it "uses the pay subdomain instead of subscriptions subdomain" do
client.refund(refund_params)

expect(WebMock).not_to have_requested(:post, /subscriptions\.solidgate\.com/)
expect(WebMock).to have_requested(:post, /pay\.solidgate\.com/)
end

it "includes Merchant header with public key" do
client.refund(refund_params)

expect(WebMock).to have_requested(:post, "https://pay.solidgate.com/api/v1/refund")
.with(headers: { "Merchant" => public_key })
end

it "includes Signature header" do
client.refund(refund_params)

expect(WebMock).to have_requested(:post, "https://pay.solidgate.com/api/v1/refund")
.with { |req| !req.headers["Signature"].nil? && !req.headers["Signature"].empty? }
end

it "returns refund response" do
result = client.refund(refund_params)
expect(result).to eq(success_response)
end
end

# ==================== Base URL Override ====================

describe "base_url parameter" do
let(:custom_base_url) { "https://custom.solidgate.com" }
let(:params) { { order_id: "order_123" } }

before do
stub_request(:post, /custom\.solidgate\.com/).to_return(
status: 200,
body: success_response.to_json,
headers: { "Content-Type" => "application/json" }
)
end

it "uses custom base_url when provided via post method" do
client.send(:post, "/api/v1/test", body: params, base_url: custom_base_url)

expect(WebMock).to have_requested(:post, "https://custom.solidgate.com/api/v1/test")
.with(body: params.to_json)
end

it "uses default api_url when base_url is nil" do
client.send(:post, "/api/v1/test", body: params, base_url: nil)

expect(WebMock).to have_requested(:post, "https://subscriptions.solidgate.com/api/v1/test")
.with(body: params.to_json)
end

it "includes proper headers when using custom base_url" do
client.send(:post, "/api/v1/test", body: params, base_url: custom_base_url)

expect(WebMock).to have_requested(:post, "https://custom.solidgate.com/api/v1/test")
.with(headers: { "Merchant" => public_key })
end

it "includes signature when using custom base_url" do
client.send(:post, "/api/v1/test", body: params, base_url: custom_base_url)

expect(WebMock).to have_requested(:post, "https://custom.solidgate.com/api/v1/test")
.with { |req| !req.headers["Signature"].nil? && !req.headers["Signature"].empty? }
end
end

# ==================== Product Methods ====================

describe "#create_product" do
Expand Down
4 changes: 2 additions & 2 deletions spec/solidgate/payment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@

describe "#refund" do
it "refunds a payment" do
expect(client).to receive(:refund_payment).with("payment_123", {})
expect(client).to receive(:refund).with(order_id: "payment_123")
payment.refund("payment_123")
end

it "refunds with specific amount and reason" do
expect(client).to receive(:refund_payment).with("payment_123", { amount: 500, reason: "Customer request" })
expect(client).to receive(:refund).with(order_id: "payment_123", amount: 500, reason: "Customer request")
payment.refund("payment_123", amount: 500, reason: "Customer request")
end
end
Expand Down
Loading