diff --git a/app/services/kbz_merchant.rb b/app/services/kbz_merchant.rb new file mode 100644 index 00000000..805759fe --- /dev/null +++ b/app/services/kbz_merchant.rb @@ -0,0 +1,99 @@ +require 'httparty' + +class KbzMerchant + class PaymentError < StandardError; end + + def initialize(payment_method) + @payment_method = payment_method + @api_url = payment_method.test_mode? ? + 'http://api.kbzpay.com/payment/gateway/uat/precreate' : + 'https://api.kbzpay.com/payment/gateway/precreate' + end + + def create_order(transaction_params) + payload = build_payload(transaction_params) + response = send_request(payload) + handle_response(response) + end + + private + + def build_payload(params) + base_params = { + method: 'kbz.payment.precreate', + timestamp: Time.now.utc.to_i.to_s, + nonce_str: SecureRandom.hex(16), + notify_url: @payment_method.notify_url, + sign_type: 'SHA256', + version: '1.0', + biz_content: { + appid: @payment_method.appid, + merch_code: @payment_method.merch_code, + merch_order_id: params[:merch_order_id], + trade_type: 'PAY_BY_QRCODE', + total_amount: params[:total_amount].to_s, + trans_currency: 'MMK', + timeout_express: params[:timeout] || '120m' + }.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) + key = 'uatfoodcourt@12' + 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=#{key}" + puts "String to sign: #{string_to_sign}" + Digest::SHA256.hexdigest(string_to_sign).upcase + end + + def send_request(payload) + headers = { + 'Content-Type' => 'application/json', + 'User-Agent' => 'KBZPay/1.0' + } + + puts "Headers: #{headers}" + puts "Payload: #{payload.to_json}" + + begin + response = HTTParty.post( + @api_url, + headers: headers, + body: { Request: payload }.to_json, + timeout: 15 + ) + + puts "Response: #{response}" + rescue HTTParty::Error => e + raise PaymentError, "HTTP error: #{e.message}" + rescue SocketError => e + raise PaymentError, "Network error: #{e.message}" + end + end + + + def handle_response(response) + json_response = JSON.parse(response.body) + if json_response.dig('Response', 'result') == 'SUCCESS' + json_response['Response'] + else + raise PaymentError, "Payment failed: #{json_response['Response']['msg']}" + end + end +end diff --git a/config/application.rb b/config/application.rb index 8fe1aca3..34a977e4 100755 --- a/config/application.rb +++ b/config/application.rb @@ -21,6 +21,8 @@ module SXRestaurants config.active_record.time_zone_aware_types = [:datetime, :time] config.active_job.queue_adapter = :sidekiq config.time_zone = 'Asia/Rangoon' + config.autoload_paths << Rails.root.join('app/services') + config.middleware.insert_before ActionDispatch::Static, Rack::Cors do allow do