200 lines
5.5 KiB
Ruby
200 lines
5.5 KiB
Ruby
require 'httparty'
|
|
|
|
class KbzMerchant
|
|
class PaymentError < StandardError; end
|
|
|
|
def initialize(payment_method)
|
|
@payment_method = payment_method
|
|
@url = @payment_method.gateway_url
|
|
json_params = @payment_method.additional_parameters.inspect.undump
|
|
params = JSON.parse(json_params)
|
|
@notify_url = params['notify_url']
|
|
@app_id = params['app_id']
|
|
end
|
|
|
|
def create_order(amount:, merch_order_id:, timeout: '120m')
|
|
api_url = "#{@url}/precreate"
|
|
payload = build_create_payload(amount, merch_order_id, timeout)
|
|
response = send_request(payload, api_url)
|
|
handle_response(response)
|
|
end
|
|
|
|
def close_order(merch_order_id:)
|
|
api_url = "#{@url}/closeorder"
|
|
payload = build_close_payload(merch_order_id)
|
|
response = send_request(payload, api_url)
|
|
handle_response(response)
|
|
end
|
|
|
|
def query_order(merch_order_id:)
|
|
api_url = "#{@url}/queryorder"
|
|
payload = build_query_payload(merch_order_id)
|
|
response = send_request(payload, api_url)
|
|
handle_response(response)
|
|
end
|
|
|
|
private
|
|
|
|
def build_create_payload(amount, merch_order_id, timeout)
|
|
base_params = {
|
|
method: 'kbz.payment.precreate',
|
|
timestamp: Time.now.utc.to_i.to_s,
|
|
nonce_str: SecureRandom.hex(16),
|
|
notify_url: @notify_url,
|
|
sign_type: 'SHA256',
|
|
version: '1.0',
|
|
biz_content: {
|
|
appid: @app_id,
|
|
merch_code: @payment_method.merchant_account_id,
|
|
merch_order_id: merch_order_id,
|
|
trade_type: 'PAY_BY_QRCODE',
|
|
total_amount: amount.to_s,
|
|
trans_currency: 'MMK',
|
|
timeout_express: timeout
|
|
}.compact
|
|
}
|
|
|
|
flattened = flatten_hash(base_params)
|
|
base_params.merge(sign: generate_signature(flattened))
|
|
end
|
|
|
|
def build_close_payload(merch_order_id)
|
|
base_params = {
|
|
method: 'kbz.payment.closeorder',
|
|
timestamp: Time.now.utc.to_i.to_s,
|
|
nonce_str: SecureRandom.hex(16),
|
|
sign_type: 'SHA256',
|
|
version: '3.0',
|
|
biz_content: {
|
|
appid: @app_id,
|
|
merch_code: @payment_method.merchant_account_id,
|
|
merch_order_id: merch_order_id
|
|
}.compact
|
|
}
|
|
|
|
flattened = flatten_hash(base_params)
|
|
base_params.merge(sign: generate_signature(flattened))
|
|
end
|
|
|
|
def build_query_payload(merch_order_id)
|
|
base_params = {
|
|
method: 'kbz.payment.queryorder',
|
|
timestamp: Time.now.utc.to_i.to_s,
|
|
nonce_str: SecureRandom.hex(16),
|
|
sign_type: 'SHA256',
|
|
version: '3.0',
|
|
biz_content: {
|
|
appid: @app_id,
|
|
merch_code: @payment_method.merchant_account_id,
|
|
merch_order_id: merch_order_id
|
|
}.compact
|
|
}
|
|
|
|
flattened = flatten_hash(base_params)
|
|
base_params.merge(sign: generate_signature(flattened))
|
|
end
|
|
|
|
def flatten_hash(hash, parent_key = nil)
|
|
hash.each_with_object({}) do |(k, v), res|
|
|
key = parent_key ? "#{k}" : k.to_s
|
|
if v.is_a?(Hash)
|
|
res.merge!(flatten_hash(v, key))
|
|
else
|
|
res[key] = v.to_s
|
|
end
|
|
end
|
|
end
|
|
|
|
def generate_signature(flattened_params)
|
|
sorted_params = flattened_params.except('sign', 'sign_type').sort
|
|
string_a = sorted_params.map { |k, v| "#{k}=#{v}" }.join('&')
|
|
puts "String a: #{string_a}"
|
|
string_to_sign = "#{string_a}&key=#{@payment_method.auth_token}"
|
|
puts "String to sign: #{string_to_sign}"
|
|
Digest::SHA256.hexdigest(string_to_sign).upcase
|
|
end
|
|
|
|
def send_request(payload, url)
|
|
headers = {
|
|
'Content-Type' => 'application/json',
|
|
'User-Agent' => 'KBZPay/1.0'
|
|
}
|
|
|
|
puts "Headers: #{headers}"
|
|
puts "Payload: #{payload.to_json}"
|
|
|
|
begin
|
|
response = HTTParty.post(
|
|
url,
|
|
headers: headers,
|
|
body: { Request: payload }.to_json,
|
|
timeout: 15
|
|
)
|
|
|
|
Rails.logger.info "Response: #{response}"
|
|
JSON.parse(response.body)
|
|
rescue HTTParty::Error => e
|
|
{
|
|
error: true,
|
|
code: 'http_error',
|
|
message: "HTTP error: #{e.message}",
|
|
alert: "Payment service unavailable. Please try again later."
|
|
}
|
|
rescue SocketError => e
|
|
{
|
|
error: true,
|
|
code: 'network_error',
|
|
message: "Network error: #{e.message}",
|
|
alert: "Network connection failed. Please check your internet."
|
|
}
|
|
rescue JSON::ParserError => e
|
|
{
|
|
error: true,
|
|
code: 'invalid_response',
|
|
message: "Invalid response format: #{e.message}",
|
|
alert: "Received invalid payment response. Please contact support."
|
|
}
|
|
rescue StandardError => e
|
|
{
|
|
error: true,
|
|
code: 'unexpected_error',
|
|
message: "Unexpected error: #{e.message}",
|
|
alert: "An unexpected error occurred. Please try again."
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
def handle_response(response)
|
|
if response['error']
|
|
{
|
|
status: 'error',
|
|
code: response['code'],
|
|
message: response['message']
|
|
}
|
|
elsif response.dig('Response', 'result') == 'SUCCESS'
|
|
{
|
|
status: 'success',
|
|
data: response['Response']
|
|
}
|
|
else
|
|
error_code = response.dig('Response', 'code')
|
|
error_message = response.dig('Response', 'msg')
|
|
case error_code
|
|
when 'OrderCenter.FAILED_CREATE_ORDER_FOR_DUPLICATED_MERCHANT_ORDER_ID'
|
|
{
|
|
status: 'failed',
|
|
code: error_code,
|
|
message: 'Duplicate order ID detected. Please use a unique reference ID.'
|
|
}
|
|
else
|
|
{
|
|
status: 'failed',
|
|
code: error_code || 'unknown_error',
|
|
message: error_message || 'Payment processing failed'
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|