# frozen_string_literal: true # Copyright (c) 2018-2020 Yegor Bugayenko # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. require 'typhoeus' require 'cgi' require 'loog' require 'zold/age' require 'zold/amount' require 'zold/id' require 'zold/txn' # WTS gateway. # # Author:: Yegor Bugayenko (yegor256@gmail.com) # Copyright:: Copyright (c) 2018-2020 Yegor Bugayenko # License:: MIT class Zold::WTS # Fake implementation. class Fake def pull 'job-id' end def balance Zold::Amount.new(zld: 4.0) end def id Zold::Id::ROOT end def pay(_keygap, _bnf, _amount, _details) 'job-id' end def usd_rate 5_000 end def find(_query) [] end def wait(_job, time: 5 * 60) raise 'Time must be positive' if time.negative? 'OK' end end # Makes a new object of the class. The key you are supposed # to obtain at this page: https://wts.zold.io/api. You will have to login # and confirm your account first. Keep this key secret, to avoid # information lost. However, even knowing the secret no payments can # be sent without they keygap. def initialize(key, log: Loog::NULL) raise 'Key can\'t be nil' if key.nil? @key = key raise 'Log can\'t be nil' if log.nil? @log = log end # Initiate PULL request. The server will pull your wallet form the network, # and make it ready for future requests. Without this operation you won't # be able to perform find() or balance() requests. # # The method returns the job ID, which you should wait for completion # using the method wait(). def pull start = Time.now job = job_of( clean( Typhoeus::Request.get( 'https://wts.zold.io/pull', headers: headers ) ) ) @log.debug("PULL job #{job} started in #{Zold::Age.new(start)}") job end # Get wallet balance. def balance start = Time.now http = clean( Typhoeus::Request.get( 'https://wts.zold.io/balance', headers: headers ) ) balance = Zold::Amount.new(zents: http.body.to_i) @log.debug("The balance #{balance} retrieved in #{Zold::Age.new(start)}") balance end # Get wallet ID. def id start = Time.now http = clean( Typhoeus::Request.get( 'https://wts.zold.io/id', headers: headers ) ) id = Zold::Id.new(http.body.to_s) @log.debug("Wallet ID #{id} retrieved in #{Zold::Age.new(start)}") id end # Initiate PAY request. The keygap is a string you get # when you confirm the account. The bnf is the name of the # GitHub account, the wallet ID or the invoice (up to you). The # amount is the amount in ZLD, e.g. "19.99". The details # is the text to add to the transaction. # # The method returns the job ID, which you should wait for completion # using the method wait(). def pay(keygap, bnf, amount, details) start = Time.now job = job_of( clean( Typhoeus::Request.post( 'https://wts.zold.io/do-pay', headers: headers, body: { keygap: keygap, bnf: bnf, amount: amount, details: details } ) ) ) @log.debug("PAY job #{job} started in #{Zold::Age.new(start)}") job end # Returns current USD rate of one ZLD. def usd_rate clean(Typhoeus::Request.get('https://wts.zold.io/usd_rate')).body.to_f end # Find transactions by the criteria. All criterias are regular expressions # and their summary result is concatenated by OR. For example, this request # will return all transactions that have "pizza" in details OR which # are coming from the root wallet: # # find(details: /pizza/, bnf: '0000000000000000') # # The method returns an array of Zold::Txn objects. def find(query) start = Time.now http = clean( Typhoeus::Request.get( 'https://wts.zold.io/find?' + query.map do |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" end.join('&'), headers: headers ) ) txns = http.body.split("\n").map { |t| Zold::Txn.parse(t) } @log.debug("#{txns.count} transactions found in #{Zold::Age.new(start)}") txns end # Wait for the job to complete. If the job completes successfully, the # method returns 'OK' in a few seconds (up to a few minutes). If the # is some error, the exception will be raised. def wait(job, time: 5 * 60) start = Time.now loop do if Time.now - start > time raise "Can't wait any longer for the job #{job} to complete" end http = Typhoeus::Request.get( 'https://wts.zold.io/job?id=' + job, headers: headers ) raise "Job #{job} not found on the server" if http.code == 404 raise "Unpredictable response code #{http.code}" unless http.code == 200 if http.body == 'Running' @log.debug("Job #{job} is still running, \ #{Zold::Age.new(start)} already...") sleep 1 next end raise http.body unless http.body == 'OK' @log.debug("Job #{job} completed, in #{Zold::Age.new(start)}") return http.body end end private def headers { 'X-Zold-WTS': @key, 'User-Agent': 'zold-ruby-sdk' } end def job_of(http) raise 'There are no headers in the response' if http.headers.nil? job = http.headers['X-Zold-Job'] raise 'Job ID is not returned, for some reason' if job.nil? job end def clean(http) error = (http.headers || {})['X-Zold-Error'] raise error unless error.nil? unless http.code == 200 || http.code == 302 @log.debug("HTTP response body: #{http.body}") raise "Unexpected response code #{http.code}" end http end end