From 41448c88f1017b2b131634578625bd746ddc0c93 Mon Sep 17 00:00:00 2001 From: mihai-aupeo Date: Fri, 27 Feb 2015 17:30:12 +0100 Subject: [PATCH 1/5] * code style changes for Ruby + Cancellation API wrapper class + .gitignore file --- .gitignore | 29 +++ lib/Paymentwall/Base.rb | 173 ++++++++++------ lib/Paymentwall/Pingback.rb | 384 +++++++++++++++--------------------- lib/Paymentwall/Product.rb | 120 +++++------ lib/Paymentwall/Ticket.rb | 47 +++++ lib/Paymentwall/Widget.rb | 334 +++++++++++++------------------ lib/paymentwall.rb | 4 +- 7 files changed, 547 insertions(+), 544 deletions(-) create mode 100644 .gitignore create mode 100644 lib/Paymentwall/Ticket.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..634ee91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Created by .ignore support plugin (hsz.mobi) +### Ruby template +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/test/tmp/ +/test/version_tmp/ +/tmp/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalisation: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +Gemfile.lock +# .ruby-version +# .ruby-gemset diff --git a/lib/Paymentwall/Base.rb b/lib/Paymentwall/Base.rb index fedf7e7..1c318dd 100755 --- a/lib/Paymentwall/Base.rb +++ b/lib/Paymentwall/Base.rb @@ -1,66 +1,111 @@ +require 'digest' + module Paymentwall - class Base - - VERSION = '1.0.0' - - API_VC = 1 - API_GOODS = 2 - API_CART = 3 - - CONTROLLER_PAYMENT_VIRTUAL_CURRENCY = 'ps' - CONTROLLER_PAYMENT_DIGITAL_GOODS = 'subscription' - CONTROLLER_PAYMENT_CART = 'cart' - - DEFAULT_SIGNATURE_VERSION = 3 - SIGNATURE_VERSION_1 = 1 - SIGNATURE_VERSION_2 = 2 - SIGNATURE_VERSION_3 = 3 - - @@apiType - @@appKey - @@secretKey - - def self.setApiType(value) - @@apiType = value - self - end - - def self.getApiType - @@apiType - end - - def self.setAppKey(value) - @@appKey = value - self - end - - def self.getAppKey - @@appKey - end - - def self.setSecretKey(value) - @@secretKey = value - self - end - - def self.getSecretKey - @@secretKey - end - - def getErrors - @errors - end - - def getErrorSummary - @errors.join("\n") - end - - protected - - def appendToErrors(err) - @errors ||=[] - @errors.push(err) - self - end - end + class Base + + VERSION = '1.0.0' + + API_VC = 1 + API_GOODS = 2 + API_CART = 3 + + CONTROLLER_PAYMENT_VIRTUAL_CURRENCY = 'ps' + CONTROLLER_PAYMENT_DIGITAL_GOODS = 'subscription' + CONTROLLER_PAYMENT_CART = 'cart' + + DEFAULT_SIGNATURE_VERSION = 3 + SIGNATURE_VERSION_1 = 1 + SIGNATURE_VERSION_2 = 2 + SIGNATURE_VERSION_3 = 3 + + @@api_type = nil + @@app_key = nil + @@secret_key = nil + + def self.setApiType(value) + @@api_type = value + self + end + + def self.getApiType + @@api_type + end + + def self.setAppKey(value) + @@app_key = value + self + end + + def self.getAppKey + @@app_key + end + + def self.setSecretKey(value) + @@secret_key = value + self + end + + def self.getSecretKey + @@secret_key + end + + def getErrors + @errors + end + + def getErrorSummary + @errors.join("\n") + end + + protected + + def self.getDefaultSignatureVersion() + return getApiType() != API_CART ? DEFAULT_SIGNATURE_VERSION : SIGNATURE_VERSION_2 + end + + def self.calculateSignature(params, secret, version) + base_string = '' + + if version == SIGNATURE_VERSION_1 + # TODO: throw exception if no uid parameter is present + + base_string += params.include?('uid') ? params['uid'] : '' + base_string += secret + + return Digest::MD5.hexdigest(base_string) + + else + + params.keys.sort.each do |name| + p = params[name] + + # converting array to hash + if p.kind_of?(Array) + p = Hash[p.map.with_index { |key, value| [value, key] }] + end + + if p.kind_of?(Hash) + p.keys.sort.each{|key| base_string += "#{name}[#{key}]=#{p[key]}"} + else + base_string += "#{name}=#{p}" + end + end + + base_string += secret + + if version == SIGNATURE_VERSION_3 + return Digest::SHA256.hexdigest(base_string) + else + return Digest::MD5.hexdigest(base_string) + end + + end + end + + def appendToErrors(err) + @errors ||=[] + @errors.push(err) + self + end + end end diff --git a/lib/Paymentwall/Pingback.rb b/lib/Paymentwall/Pingback.rb index b38f4b5..4a5122a 100755 --- a/lib/Paymentwall/Pingback.rb +++ b/lib/Paymentwall/Pingback.rb @@ -1,224 +1,164 @@ module Paymentwall - class Pingback < Paymentwall::Base - - PINGBACK_TYPE_REGULAR = 0 - PINGBACK_TYPE_GOODWILL = 1 - PINGBACK_TYPE_NEGATIVE = 2 - - def initialize(parameters = {}, ipAddress = '') - @parameters = parameters - @ipAddress = ipAddress - end - - def validate(skipIpWhitelistCheck = false) - validated = false - - if self.isParametersValid() - if self.isIpAddressValid() || skipIpWhitelistCheck - if self.isSignatureValid() - validated = true - else - self.appendToErrors('Wrong signature') - end - else - self.appendToErrors('IP address is not whitelisted') - end - else - self.appendToErrors('Missing parameters') - end - - validated - end - - def isSignatureValid() - signatureParamsToSign = {} - - if self.class::getApiType() == self.class::API_VC - signatureParams = Array['uid', 'currency', 'type', 'ref'] - elsif self.class::getApiType() == self.class::API_GOODS - signatureParams = Array['uid', 'goodsid', 'slength', 'speriod', 'type', 'ref'] - else - signatureParams = Array['uid', 'goodsid', 'type', 'ref'] - @parameters['sign_version'] = self.class::SIGNATURE_VERSION_2 - - end - - if !@parameters.include?('sign_version') || @parameters['sign_version'].to_i == self.class::SIGNATURE_VERSION_1 - signatureParams.each do |field| - signatureParamsToSign[field] = @parameters.include?(field) ? @parameters[field] : nil - end - - @parameters['sign_version'] = self.class::SIGNATURE_VERSION_1 - - else - signatureParamsToSign = @parameters - end - - signatureCalculated = self.calculateSignature(signatureParamsToSign, self.class::getSecretKey(), @parameters['sign_version']) - - signature = @parameters.include?('sig') ? @parameters['sig'] : nil - - signature == signatureCalculated - end - - def isIpAddressValid() - ipsWhitelist = [ - '174.36.92.186', - '174.36.96.66', - '174.36.92.187', - '174.36.92.192', - '174.37.14.28' - ] - - ipsWhitelist.include? @ipAddress - end - - def isParametersValid() - errorsNumber = 0 - requiredParams = [] - - if self.class::getApiType() == self.class::API_VC - requiredParams = ['uid', 'currency', 'type', 'ref', 'sig'] - elsif self.class::getApiType() == self.class::API_GOODS - requiredParams = ['uid', 'goodsid', 'type', 'ref', 'sig'] - else - requiredParams = ['uid', 'goodsid', 'type', 'ref', 'sig'] - end - - requiredParams.each do |field| - if !@parameters.include?(field) # || $parameters[field] === '' - self.appendToErrors("Parameter #{field} is missing") - errorsNumber += 1 - end - end - - errorsNumber == 0 - end - - def getParameter(param) - if @parameters.include?(param) - return @parameters[param] - else - return nil - end - end - - def getType() - pingbackTypes = [ - self.class::PINGBACK_TYPE_REGULAR, - self.class::PINGBACK_TYPE_GOODWILL, - self.class::PINGBACK_TYPE_NEGATIVE - ] - - if @parameters.include?('type') - if pingbackTypes.include?(@parameters['type'].to_i) - return @parameters['type'].to_i - end - end - - return nil - end - - def getUserId - self.getParameter('uid').to_s - end - - def getVirtualCurrencyAmount() - self.getParameter('currency').to_i - end - - def getProductId() - self.getParameter('goodsid').to_s - end - - def getProductPeriodLength() - self.getParameter('slength').to_i - end - - def getProductPeriodType() - self.getParameter('speriod').to_s - end - - def getProduct() - Paymentwall::Product.new( - self.getProductId(), - 0, - nil, - nil, - self.getProductPeriodLength() > 0 ? Paymentwall::Product::TYPE_SUBSCRIPTION : Paymentwall::Product::TYPE_FIXED, - self.getProductPeriodLength(), - self.getProductPeriodType() - ) - end - - def getProducts() - result = [] - productIds = self.getParameter('goodsid') - - if productIds.kind_of?(Array) && productIds.length > 0 - productIds.each do |id| - result.push(Paymentwall::Product.new(id)) - end - end - - return result - end - - def getReferenceId() - self.getParameter('ref').to_s - end - - def getPingbackUniqueId() - self.getReferenceId().to_s + '_' + self.getType().to_s - end - - def isDeliverable() - self.getType() == self.class::PINGBACK_TYPE_REGULAR || self.getType() == self.class::PINGBACK_TYPE_GOODWILL - end - - def isCancelable() - self.getType() == self.class::PINGBACK_TYPE_NEGATIVE - end - - protected - - def calculateSignature(params, secret, version) - - params = params.clone - params.delete('sig') - - sortKeys = (version.to_i == self.class::SIGNATURE_VERSION_2 or version.to_i == self.class::SIGNATURE_VERSION_3) - keys = sortKeys ? params.keys.sort : params.keys - - baseString = '' - - keys.each do |name| - p = params[name] - - # converting array to hash - if p.kind_of?(Array) - p = Hash[p.map.with_index { |key, value| [value, key] }] - end - - if p.kind_of?(Hash) - subKeys = sortKeys ? p.keys.sort : p.keys; - subKeys.each do |key| - value = p[key] - baseString += "#{name}[#{key}]=#{value}" - end - else - baseString += "#{name}=#{p}" - end - end - - baseString += secret - - require 'digest' - if version.to_i == self.class::SIGNATURE_VERSION_3 - return Digest::SHA256.hexdigest(baseString) - else - return Digest::MD5.hexdigest(baseString) - end - end - end + class Pingback < Paymentwall::Base + + PINGBACK_TYPE_REGULAR = 0 + PINGBACK_TYPE_GOODWILL = 1 + PINGBACK_TYPE_NEGATIVE = 2 + + def initialize(parameters = {}, ipAddress = '') + @parameters = parameters + @ip_address = ipAddress + end + + def validate(skipIpWhitelistCheck = false) + validated = false + + if isParametersValid() + if isIpAddressValid() || skipIpWhitelistCheck + if isSignatureValid() + validated = true + else + appendToErrors('Wrong signature') + end + else + appendToErrors('IP address is not whitelisted') + end + else + appendToErrors('Missing parameters') + end + + validated + end + + def isSignatureValid() + signature_params_to_sign = {} + + if self.class.getApiType() == API_VC + signature_params = %w(uid currency type ref) + elsif self.class.getApiType() == API_GOODS + signature_params = %w(uid goodsid slength speriod type ref) + else + signature_params = %w(uid goodsid type ref) + @parameters['sign_version'] = SIGNATURE_VERSION_2 + end + + if !@parameters.include?('sign_version') || @parameters['sign_version'].to_i == SIGNATURE_VERSION_1 + signature_params.each do |field| + signature_params_to_sign[field] = @parameters.include?(field) ? @parameters[field] : nil + end + @parameters['sign_version'] = SIGNATURE_VERSION_1 + else + signature_params_to_sign = @parameters + end + + signature_calculated = self.calculateSignature(signature_params_to_sign, self.class.getSecretKey(), @parameters['sign_version']) + + signature = @parameters.include?('sig') ? @parameters['sig'] : nil + + signature == signature_calculated + end + + def isIpAddressValid() + ip_whitelist = %w(174.36.92.186 174.36.96.66 174.36.92.187 174.36.92.192 174.37.14.28) + + ip_whitelist.include? @ip_address + end + + def isParametersValid() + errors_number = 0 + required_params = [] + + if self.class::getApiType() == self.class::API_VC + required_params = %w(uid currency type ref sig) + elsif self.class::getApiType() == self.class::API_GOODS + required_params = %w(uid goodsid type ref sig) + else + required_params = %w(uid goodsid type ref sig) + end + + required_params.each do |field| + if !@parameters.include?(field) # || $parameters[field] === '' + self.appendToErrors("Parameter #{field} is missing") + errors_number += 1 + end + end + + errors_number == 0 + end + + def getParameter(param) + return @parameters[param] if @parameters.include?(param) + nil + end + + def getType() + pingback_types = [ + PINGBACK_TYPE_REGULAR, + PINGBACK_TYPE_GOODWILL, + PINGBACK_TYPE_NEGATIVE + ] + + return @parameters['type'].to_i if @parameters.include?('type') && pingback_types.include?(@parameters['type'].to_i) + nil + end + + def getUserId + getParameter('uid').to_s + end + + def getVirtualCurrencyAmount() + getParameter('currency').to_i + end + + def getProductId() + getParameter('goodsid').to_s + end + + def getProductPeriodLength() + getParameter('slength').to_i + end + + def getProductPeriodType() + getParameter('speriod').to_s + end + + def getProduct() + Paymentwall::Product.new( + self.getProductId(), + 0, + nil, + nil, + self.getProductPeriodLength() > 0 ? Paymentwall::Product::TYPE_SUBSCRIPTION : Paymentwall::Product::TYPE_FIXED, + self.getProductPeriodLength(), + self.getProductPeriodType() + ) + end + + def getProducts() + product_ids = self.getParameter('goodsid') + + if product_ids.kind_of?(Array) && product_ids.length > 0 + product_ids.map{|id| Paymentwall::Product.new(id) } + else + [] + end + end + + def getReferenceId() + getParameter('ref').to_s + end + + def getPingbackUniqueId() + getReferenceId().to_s + '_' + getType().to_s + end + + def isDeliverable() + getType() == PINGBACK_TYPE_REGULAR || getType() == PINGBACK_TYPE_GOODWILL + end + + def isCancelable() + getType() == PINGBACK_TYPE_NEGATIVE + end + + end end diff --git a/lib/Paymentwall/Product.rb b/lib/Paymentwall/Product.rb index 1b7a53f..05f63d3 100755 --- a/lib/Paymentwall/Product.rb +++ b/lib/Paymentwall/Product.rb @@ -1,62 +1,62 @@ module Paymentwall - class Product - - TYPE_SUBSCRIPTION = 'subscription' - TYPE_FIXED = 'fixed' - - PERIOD_TYPE_DAY = 'day' - PERIOD_TYPE_WEEK = 'week' - PERIOD_TYPE_MONTH = 'month' - PERIOD_TYPE_YEAR = 'year' - - def initialize(productId, amount = 0.0, currencyCode = nil, name = nil, productType = self.class::TYPE_FIXED, periodLength = 0, periodType = nil, recurring = false, trialProduct = nil) - @productId = productId - @amount = amount.round(2) - @currencyCode = currencyCode - @name = name - @productType = productType - @periodLength = periodLength - @periodType = periodType - @recurring = recurring - if (productType == Paymentwall::Product::TYPE_SUBSCRIPTION && recurring && recurring != 0) - @trialProduct = trialProduct - end - end - - def getId() - @productId - end - - def getAmount() - @amount - end - - def getCurrencyCode - @currencyCode - end - - def getName() - @name - end - - def getType() - @productType - end - - def getPeriodType() - @periodType - end - - def getPeriodLength() - @periodLength - end - - def isRecurring() - @recurring - end - - def getTrialProduct() - @trialProduct - end - end + class Product + + TYPE_SUBSCRIPTION = 'subscription' + TYPE_FIXED = 'fixed' + + PERIOD_TYPE_DAY = 'day' + PERIOD_TYPE_WEEK = 'week' + PERIOD_TYPE_MONTH = 'month' + PERIOD_TYPE_YEAR = 'year' + + def initialize(productId, amount = 0.0, currencyCode = nil, name = nil, productType = self.class::TYPE_FIXED, periodLength = 0, periodType = nil, recurring = false, trialProduct = nil) + @product_id = productId + @amount = amount.round(2) + @currency_code = currencyCode + @name = name + @product_type = productType + @period_length = periodLength + @period_type = periodType + @recurring = recurring + if (productType == Paymentwall::Product::TYPE_SUBSCRIPTION && recurring && recurring != 0) + @trial_product = trialProduct + end + end + + def getId() + @product_id + end + + def getAmount() + @amount + end + + def getCurrencyCode + @currency_code + end + + def getName() + @name + end + + def getType() + @product_type + end + + def getPeriodType() + @period_type + end + + def getPeriodLength() + @period_length + end + + def isRecurring() + @recurring + end + + def getTrialProduct() + @trial_product + end + end end diff --git a/lib/Paymentwall/Ticket.rb b/lib/Paymentwall/Ticket.rb new file mode 100644 index 0000000..1a231c0 --- /dev/null +++ b/lib/Paymentwall/Ticket.rb @@ -0,0 +1,47 @@ +require 'json' +require 'ostruct' +require 'net/http' +require 'net/https' + +module Paymentwall + # Wrapper class for the Utility Cancellation API + class Ticket < Paymentwall::Base + + BASE_URL = 'https://api.paymentwall.com/developers/api/ticket' + + TYPE_REFUND = 1 + TYPE_CANCELLATION = 2 + TYPE_OTHER = 3 + + # Request a cancellation ticket + # @param type [Integer] Accepts one of the TYPE_XXX constant values + # @param user_id [Integer|String] User ID + # @param reference [String] Transaction reference ID + # @param extra_params [Hash] Additional parameters for the call + def self.create(type, user_id, reference, extra_params = {}) + raise ArgumentError.new 'Either user_id or reference is required.' unless user_id || reference + params = { + 'key' => self.getAppKey(), + 'type' => type + } + params.update('uid' => user_id) if user_id + params.update('ref' => reference) if reference + + params['sign_version'] = signatureVersion = self.getDefaultSignatureVersion() + + if extra_params.include?('sign_version') + signatureVersion = params['sign_version'] = extra_params['sign_version'] + end + + params = params.merge(extra_params) + + params['sign'] = self.calculateSignature(params, self.getSecretKey(), signatureVersion) + + uri = URI.parse(BASE_URL) + response = Net::HTTP::post_form(uri, params) + + return OpenStruct.new(JSON.parse(response.body)) if response.code.to_i == 200 + nil + end + end +end \ No newline at end of file diff --git a/lib/Paymentwall/Widget.rb b/lib/Paymentwall/Widget.rb index a3c7331..53b741f 100755 --- a/lib/Paymentwall/Widget.rb +++ b/lib/Paymentwall/Widget.rb @@ -1,199 +1,139 @@ module Paymentwall - class Widget < Paymentwall::Base - - BASE_URL = 'https://api.paymentwall.com/api' - - def initialize(userId, widgetCode, products = [], extraParams = {}) - @userId = userId - @widgetCode = widgetCode - @extraParams = extraParams - @products = products - end - - def getDefaultSignatureVersion() - return self.class::getApiType() != self.class::API_CART ? self.class::DEFAULT_SIGNATURE_VERSION : self.class::SIGNATURE_VERSION_2 - end - - - def getUrl() - params = { - 'key' => self.class::getAppKey(), - 'uid' => @userId, - 'widget' => @widgetCode - } - - productsNumber = @products.count() - - if self.class::getApiType() == self.class::API_GOODS - - if @products.kind_of?(Array) - - if productsNumber == 1 - product = @products[0] - if product.kind_of?(Paymentwall::Product) - - postTrialProduct = nil - if product.getTrialProduct().kind_of?(Paymentwall::Product) - postTrialProduct = product - product = product.getTrialProduct() - end - - params['amount'] = product.getAmount() - params['currencyCode'] = product.getCurrencyCode() - params['ag_name'] = product.getName() - params['ag_external_id'] = product.getId() - params['ag_type'] = product.getType() - - if product.getType() == Paymentwall::Product::TYPE_SUBSCRIPTION - params['ag_period_length'] = product.getPeriodLength() - params['ag_period_type'] = product.getPeriodType() - if product.isRecurring() - - params['ag_recurring'] = product.isRecurring() ? 1 : 0 - - if postTrialProduct - params['ag_trial'] = 1; - params['ag_post_trial_external_id'] = postTrialProduct.getId() - params['ag_post_trial_period_length'] = postTrialProduct.getPeriodLength() - params['ag_post_trial_period_type'] = postTrialProduct.getPeriodType() - params['ag_post_trial_name'] = postTrialProduct.getName() - params['post_trial_amount'] = postTrialProduct.getAmount() - params['post_trial_currencyCode'] = postTrialProduct.getCurrencyCode() - end - end - end - else - #TODO: self.appendToErrors('Not an instance of Paymentwall::Product') - end - else - #TODO: self.appendToErrors('Only 1 product is allowed in flexible widget call') - end - - end - - elsif self.class::getApiType() == self.class::API_CART - index = 0 - @products.each do |product| - params['external_ids[' + index.to_s + ']'] = product.getId() - - if product.getAmount() > 0 - params['prices[' + index.to_s + ']'] = product.getAmount() - end - if product.getCurrencyCode() != '' && product.getCurrencyCode() != nil - params['currencies[' + index.to_s + ']'] = product.getCurrencyCode() - end - index += 1 - end - end - - params['sign_version'] = signatureVersion = self.getDefaultSignatureVersion() - - if @extraParams.include?('sign_version') - signatureVersion = params['sign_version'] = @extraParams['sign_version'] - end - - params = params.merge(@extraParams) - - params['sign'] = self.class.calculateSignature(params, self.class::getSecretKey(), signatureVersion) - - return self.class::BASE_URL + '/' + self.buildController(@widgetCode) + '?' + self.http_build_query(params) - end - - def getHtmlCode(attributes = {}) - defaultAttributes = { - 'frameborder' => '0', - 'width' => '750', - 'height' => '800' - } - - attributes = defaultAttributes.merge(attributes) - - attributesQuery = '' - attributes.each do |attr, value| - attributesQuery += ' ' + attr.to_s + '="' + value.to_s + '"' - end - - return '' - end - - def self.calculateSignature(params, secret, version) - require 'digest' - baseString = '' - - if version == self::SIGNATURE_VERSION_1 - # TODO: throw exception if no uid parameter is present - - baseString += params.include?('uid') ? params['uid'] : '' - baseString += secret - - return Digest::MD5.hexdigest(baseString) - - else - - keys = params.keys.sort - - keys.each do |name| - p = params[name] - - # converting array to hash - if p.kind_of?(Array) - p = Hash[p.map.with_index { |key, value| [value, key] }] - end - - if p.kind_of?(Hash) - subKeys = p.keys.sort - subKeys.each do |key| - value = p[key] - baseString += "#{name}[#{key}]=#{value}" - end - else - baseString += "#{name}=#{p}" - end - end - - baseString += secret - - if version == self::SIGNATURE_VERSION_3 - return Digest::SHA256.hexdigest(baseString) - else - return Digest::MD5.hexdigest(baseString) - end - - end - end - - protected - - def buildController(widget, flexibleCall = false) - if self.class::getApiType() == self.class::API_VC - if !/^w|s|mw/.match(widget) - return self.class::CONTROLLER_PAYMENT_VIRTUAL_CURRENCY - end - elsif self.class::getApiType() == self.class::API_GOODS - if !flexibleCall - if !/^w|s|mw/.match(widget) - return self.class::CONTROLLER_PAYMENT_DIGITAL_GOODS - end - else - return self.class::CONTROLLER_PAYMENT_DIGITAL_GOODS - end - else - return self.class::CONTROLLER_PAYMENT_CART - end - - return '' - end - - def http_build_query(params) - result = []; - params.each do |key, value| - result.push(key + '=' + self.url_encode(value)) - end - return result.join('&') - end - - def url_encode(value) - URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) - end - end + class Widget < Paymentwall::Base + + BASE_URL = 'https://api.paymentwall.com/api' + + def initialize(userId, widgetCode, products = [], extraParams = {}) + @user_id = userId + @widget_code = widgetCode + @extra_params = extraParams + @products = products + end + + def getUrl() + params = { + 'key' => self.class.getAppKey(), + 'uid' => @user_id, + 'widget' => @widget_code + } + + products_number = @products.count() + + if self.class::getApiType() == API_GOODS + + if @products.kind_of?(Array) + + if products_number == 1 + product = @products[0] + if product.kind_of?(Paymentwall::Product) + + post_trial_product = nil + if product.getTrialProduct().kind_of?(Paymentwall::Product) + post_trial_product = product + product = product.getTrialProduct() + end + + params['amount'] = product.getAmount() + params['currencyCode'] = product.getCurrencyCode() + params['ag_name'] = product.getName() + params['ag_external_id'] = product.getId() + params['ag_type'] = product.getType() + + if product.getType() == Paymentwall::Product::TYPE_SUBSCRIPTION + params['ag_period_length'] = product.getPeriodLength() + params['ag_period_type'] = product.getPeriodType() + if product.isRecurring() + + params['ag_recurring'] = product.isRecurring() ? 1 : 0 + + if post_trial_product + params['ag_trial'] = 1; + params['ag_post_trial_external_id'] = post_trial_product.getId() + params['ag_post_trial_period_length'] = post_trial_product.getPeriodLength() + params['ag_post_trial_period_type'] = post_trial_product.getPeriodType() + params['ag_post_trial_name'] = post_trial_product.getName() + params['post_trial_amount'] = post_trial_product.getAmount() + params['post_trial_currencyCode'] = post_trial_product.getCurrencyCode() + end + end + end + else + #TODO: self.appendToErrors('Not an instance of Paymentwall::Product') + end + else + #TODO: self.appendToErrors('Only 1 product is allowed in flexible widget call') + end + + end + + elsif self.class.getApiType() == API_CART + index = 0 + @products.each do |product| + params['external_ids[' + index.to_s + ']'] = product.getId() + + if product.getAmount() > 0 + params['prices[' + index.to_s + ']'] = product.getAmount() + end + if product.getCurrencyCode() != '' && product.getCurrencyCode() != nil + params['currencies[' + index.to_s + ']'] = product.getCurrencyCode() + end + index += 1 + end + end + + params['sign_version'] = signature_version = self.class.getDefaultSignatureVersion() + + if @extra_params.include?('sign_version') + signature_version = params['sign_version'] = @extra_params['sign_version'] + end + + params = params.merge(@extra_params) + + params['sign'] = self.class.calculateSignature(params, self.class.getSecretKey(), signature_version) + + "#{BASE_URL}/#{buildController(@widget_code)}?#{http_build_query(params)}" + end + + def getHtmlCode(attributes = {}) + default_attributes = { + 'frameborder' => '0', + 'width' => '750', + 'height' => '800' + } + + attributes = default_attributes.merge(attributes) + + attributes_query = attributes.map{|attr, value| "#{attr.to_s}=\"#{value.to_s}\"" }.join(' ') + + '' + end + + protected + + def buildController(widget, flexibleCall = false) + if self.class.getApiType() == API_VC + unless /^w|s|mw/.match(widget) + return CONTROLLER_PAYMENT_VIRTUAL_CURRENCY + end + elsif self.class.getApiType() == API_GOODS + unless flexibleCall && /^w|s|mw/.match(widget) + return CONTROLLER_PAYMENT_DIGITAL_GOODS + else + return CONTROLLER_PAYMENT_DIGITAL_GOODS + end + else + return CONTROLLER_PAYMENT_CART + end + + '' + end + + def http_build_query(params) + params.map{|key,value| "#{key}=#{url_encode(value)}" }.join('&') + end + + def url_encode(value) + URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) + end + end end diff --git a/lib/paymentwall.rb b/lib/paymentwall.rb index b608e08..52de554 100755 --- a/lib/paymentwall.rb +++ b/lib/paymentwall.rb @@ -2,4 +2,6 @@ require_relative 'Paymentwall/Base.rb' require_relative 'Paymentwall/Pingback.rb' require_relative 'Paymentwall/Product.rb' -require_relative 'Paymentwall/Widget.rb' \ No newline at end of file +require_relative 'Paymentwall/Widget.rb' + +require_relative 'Paymentwall/Ticket.rb' \ No newline at end of file From 6606096a1953e92754bc2e811f136d5b6bd3c986 Mon Sep 17 00:00:00 2001 From: mihai-aupeo Date: Tue, 10 Mar 2015 11:22:01 +0100 Subject: [PATCH 2/5] + gemspec file --- paymentwall.gemspec | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 paymentwall.gemspec diff --git a/paymentwall.gemspec b/paymentwall.gemspec new file mode 100644 index 0000000..6249dfe --- /dev/null +++ b/paymentwall.gemspec @@ -0,0 +1,28 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |spec| + spec.name = 'analytics_service-client' + spec.version = '1.0.0' + spec.authors = %w(paymentwall-dev ivan-kovalyov saks mihai-aupeo) + spec.email = %w(devsupport@paymentwall.com) + spec.description = %q{PaymentWall Ruby} + spec.summary = %q{This library allows developers to use Paymentwall APIs (Virtual Currency, Digital Goods featuring recurring billing, and Virtual Cart)} + spec.homepage = %q{http://www.paymentwall.com} + spec.license = 'MIT' + + spec.files = Dir['lib/**/*.rb'] #`git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = %w(lib) + + spec.add_development_dependency 'bundler', '~> 1.3' + # spec.add_development_dependency 'rake' + # spec.add_development_dependency 'rspec', '~> 3.0' + # spec.add_development_dependency 'simplecov' + # + # spec.add_development_dependency 'yard' + # spec.add_development_dependency 'redcarpet' + # spec.add_development_dependency 'github-markup' +end \ No newline at end of file From 9d3018fcd442444041ffc8f6db31fce7c14dad38 Mon Sep 17 00:00:00 2001 From: mihai-aupeo Date: Tue, 10 Mar 2015 11:22:44 +0100 Subject: [PATCH 3/5] + gemspec file --- paymentwall.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paymentwall.gemspec b/paymentwall.gemspec index 6249dfe..eb26ff6 100644 --- a/paymentwall.gemspec +++ b/paymentwall.gemspec @@ -3,7 +3,7 @@ lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |spec| - spec.name = 'analytics_service-client' + spec.name = 'paymentwall' spec.version = '1.0.0' spec.authors = %w(paymentwall-dev ivan-kovalyov saks mihai-aupeo) spec.email = %w(devsupport@paymentwall.com) From 9dad87cb3fb303e80f5939528b84f4346dba22a1 Mon Sep 17 00:00:00 2001 From: mihai-aupeo Date: Tue, 24 Mar 2015 10:54:50 +0100 Subject: [PATCH 4/5] * fixed Pingback signature validation --- lib/Paymentwall/Pingback.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Paymentwall/Pingback.rb b/lib/Paymentwall/Pingback.rb index 4a5122a..1ddec9f 100755 --- a/lib/Paymentwall/Pingback.rb +++ b/lib/Paymentwall/Pingback.rb @@ -51,7 +51,7 @@ def isSignatureValid() signature_params_to_sign = @parameters end - signature_calculated = self.calculateSignature(signature_params_to_sign, self.class.getSecretKey(), @parameters['sign_version']) + signature_calculated = self.class.calculateSignature(signature_params_to_sign, self.class.getSecretKey(), @parameters['sign_version']) signature = @parameters.include?('sig') ? @parameters['sig'] : nil From 20e7cbc4316b12245f051ed9ba8b9e793fc1b131 Mon Sep 17 00:00:00 2001 From: mihai-aupeo Date: Wed, 8 Apr 2015 10:49:43 +0200 Subject: [PATCH 5/5] * fixed signature calculation; methods are different + added Gemfile + added rspec and simplecov --- .rspec | 2 + Gemfile | 4 ++ lib/Paymentwall/Base.rb | 39 ---------------- lib/Paymentwall/Pingback.rb | 41 +++++++++++++++++ lib/Paymentwall/Ticket.rb | 41 +++++++++++++++++ lib/Paymentwall/Widget.rb | 39 ++++++++++++++++ paymentwall.gemspec | 4 +- spec/spec_helper.rb | 88 +++++++++++++++++++++++++++++++++++++ 8 files changed, 217 insertions(+), 41 deletions(-) create mode 100644 .rspec create mode 100644 Gemfile create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..07ede88 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in paymentwall.gemspec +gemspec diff --git a/lib/Paymentwall/Base.rb b/lib/Paymentwall/Base.rb index 1c318dd..7e8d988 100755 --- a/lib/Paymentwall/Base.rb +++ b/lib/Paymentwall/Base.rb @@ -63,45 +63,6 @@ def self.getDefaultSignatureVersion() return getApiType() != API_CART ? DEFAULT_SIGNATURE_VERSION : SIGNATURE_VERSION_2 end - def self.calculateSignature(params, secret, version) - base_string = '' - - if version == SIGNATURE_VERSION_1 - # TODO: throw exception if no uid parameter is present - - base_string += params.include?('uid') ? params['uid'] : '' - base_string += secret - - return Digest::MD5.hexdigest(base_string) - - else - - params.keys.sort.each do |name| - p = params[name] - - # converting array to hash - if p.kind_of?(Array) - p = Hash[p.map.with_index { |key, value| [value, key] }] - end - - if p.kind_of?(Hash) - p.keys.sort.each{|key| base_string += "#{name}[#{key}]=#{p[key]}"} - else - base_string += "#{name}=#{p}" - end - end - - base_string += secret - - if version == SIGNATURE_VERSION_3 - return Digest::SHA256.hexdigest(base_string) - else - return Digest::MD5.hexdigest(base_string) - end - - end - end - def appendToErrors(err) @errors ||=[] @errors.push(err) diff --git a/lib/Paymentwall/Pingback.rb b/lib/Paymentwall/Pingback.rb index 1ddec9f..832ea6f 100755 --- a/lib/Paymentwall/Pingback.rb +++ b/lib/Paymentwall/Pingback.rb @@ -160,5 +160,46 @@ def isCancelable() getType() == PINGBACK_TYPE_NEGATIVE end + protected + + def self.calculateSignature(params, secret, version) + + params = params.clone + params.delete('sig') + + sort_keys = (version.to_i == SIGNATURE_VERSION_2 or version.to_i == SIGNATURE_VERSION_3) + keys = sort_keys ? params.keys.sort : params.keys + + base_string = '' + + keys.each do |name| + p = params[name] + + # converting array to hash + if p.kind_of?(Array) + p = Hash[p.map.with_index { |key, value| [value, key] }] + end + + if p.kind_of?(Hash) + sub_keys = sort_keys ? p.keys.sort : p.keys; + sub_keys.each do |key| + value = p[key] + base_string += "#{name}[#{key}]=#{value}" + end + else + base_string += "#{name}=#{p}" + end + end + + base_string += secret + + require 'digest' + if version.to_i == SIGNATURE_VERSION_3 + return Digest::SHA256.hexdigest(base_string) + else + return Digest::MD5.hexdigest(base_string) + end + end + end end diff --git a/lib/Paymentwall/Ticket.rb b/lib/Paymentwall/Ticket.rb index 1a231c0..f8c8bc2 100644 --- a/lib/Paymentwall/Ticket.rb +++ b/lib/Paymentwall/Ticket.rb @@ -43,5 +43,46 @@ def self.create(type, user_id, reference, extra_params = {}) return OpenStruct.new(JSON.parse(response.body)) if response.code.to_i == 200 nil end + + protected + + def self.calculateSignature(params, secret, version) + base_string = '' + + if version == SIGNATURE_VERSION_1 + # TODO: throw exception if no uid parameter is present + + base_string += params.include?('uid') ? params['uid'] : '' + base_string += secret + + return Digest::MD5.hexdigest(base_string) + + else + + params.keys.sort.each do |name| + p = params[name] + + # converting array to hash + if p.kind_of?(Array) + p = Hash[p.map.with_index { |key, value| [value, key] }] + end + + if p.kind_of?(Hash) + p.keys.sort.each{|key| base_string += "#{name}[#{key}]=#{p[key]}"} + else + base_string += "#{name}=#{p}" + end + end + + base_string += secret + + if version == SIGNATURE_VERSION_3 + return Digest::SHA256.hexdigest(base_string) + else + return Digest::MD5.hexdigest(base_string) + end + + end + end end end \ No newline at end of file diff --git a/lib/Paymentwall/Widget.rb b/lib/Paymentwall/Widget.rb index 53b741f..d42cd1b 100755 --- a/lib/Paymentwall/Widget.rb +++ b/lib/Paymentwall/Widget.rb @@ -135,5 +135,44 @@ def http_build_query(params) def url_encode(value) URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) end + + def self.calculateSignature(params, secret, version) + base_string = '' + + if version == SIGNATURE_VERSION_1 + # TODO: throw exception if no uid parameter is present + + base_string += params.include?('uid') ? params['uid'] : '' + base_string += secret + + return Digest::MD5.hexdigest(base_string) + + else + + params.keys.sort.each do |name| + p = params[name] + + # converting array to hash + if p.kind_of?(Array) + p = Hash[p.map.with_index { |key, value| [value, key] }] + end + + if p.kind_of?(Hash) + p.keys.sort.each{|key| base_string += "#{name}[#{key}]=#{p[key]}"} + else + base_string += "#{name}=#{p}" + end + end + + base_string += secret + + if version == SIGNATURE_VERSION_3 + return Digest::SHA256.hexdigest(base_string) + else + return Digest::MD5.hexdigest(base_string) + end + + end + end end end diff --git a/paymentwall.gemspec b/paymentwall.gemspec index eb26ff6..6c130b9 100644 --- a/paymentwall.gemspec +++ b/paymentwall.gemspec @@ -19,8 +19,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 1.3' # spec.add_development_dependency 'rake' - # spec.add_development_dependency 'rspec', '~> 3.0' - # spec.add_development_dependency 'simplecov' + spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'simplecov' # # spec.add_development_dependency 'yard' # spec.add_development_dependency 'redcarpet' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..1645551 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,88 @@ +require 'rspec' +require 'spec_data' + + +require 'simplecov' +SimpleCov.root(File.expand_path( File.join(File.dirname(__FILE__), '..') ) ) +SimpleCov.start do + add_filter '/.idea/' + add_filter '/config/' + add_filter '/log/' + add_filter '/spec/' + + add_group 'Library', 'lib/Paymentwall' +end if ENV['COVERAGE'] + +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end