paynow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2c72ebbaf1cb6a4415c9f3341bdfe0212d30a72e1ae8583437dc9c77fc038639
4
+ data.tar.gz: 04d35972c4aa846a8859ec60f177167a6593a70874dced3a0165fd8fef6483e9
5
+ SHA512:
6
+ metadata.gz: bbbd3ad28e9c700b71efc716360c204ac9a63f2cbba55615ef0d660f8dc3a0b823b69f58d0d883bc962f84a2889a8aee69afe949bedf069854bbebcbd1b6e910
7
+ data.tar.gz: 4be4ec1a05f13b4276f1c524370b28ee5e4943ff1f4e3c86a9c6f3f81faf32e81a74a8594df793f17d9ed621f7ba390262b13de2c8ebc3a0a499394c4d7bad39
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Blessing Muchaya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Paynow ZW
2
+ Paynow ZW is a simple and slightly opinionated Ruby abstraction of the Paynow API
3
+
4
+ The aim of this library is to provide a clean interface when interacting with the PAYNOW API. Wrappers in other languages offer a similar interface to create payments but Paynow ZW is simpler, it unifies creating regular and express payments.
5
+
6
+ ## Installation ##
7
+
8
+ Installation from rubygems:
9
+
10
+ ```bash
11
+ gem install paynow-zw
12
+ ```
13
+
14
+ Or, if you're using bundler, add the following line to your Gemfile:
15
+
16
+ ```bash
17
+ gem 'paynow-zw'
18
+ ```
19
+
20
+ And then run:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ Run the following generator:
27
+
28
+ ```bash
29
+ rails g paynow:install
30
+ ```
31
+
32
+ Finally, head over to config/initializers/paynow.rb. Uncomment the configs and alter them with values applicable to your application.
33
+
34
+ ## Usage ##
35
+
36
+ The following example demonstrates how to do a payment using Paynow ZW
37
+
38
+ ```ruby
39
+ result = Paynow::Payment.create(
40
+ amount: 20.21,
41
+ reference: 1984,
42
+ method: "pay_with_paynow",
43
+ additional_info: "Dance to the end of love weekend pass"
44
+ )
45
+
46
+ if result.success?
47
+ #some so order stuff
48
+ redirect_to paynow_redirect_url
49
+ else
50
+ puts result.status
51
+ end
52
+ ```
53
+
54
+ The next example demonstrates how to do an express payment using Paynow ZW
55
+
56
+ ```ruby
57
+ result = Paynow::Payment.create(
58
+ amount: 19.98,
59
+ reference: 405,
60
+ method: "ecocash",
61
+ phone: "0771111111",
62
+ authemail: "bles@heyheyhey.com",
63
+ additional_info: "The Hanging Gardens of Beatenberg"
64
+ )
65
+
66
+ if result.success?
67
+ #do some order stuff
68
+ #poll the status of the payment using the poll_url method ie. result.poll_url
69
+ end
70
+ ```
71
+ You can use **pay_with_paynow** to redirect to Paynow as shown in the first example. You can also use **poll_url** to check for the status of both express and basic payments.
72
+
73
+
74
+ You can only specify four payment methods. The 'pay_with_paynow' option redirects to paynow.
75
+
76
+ Method | Value
77
+ ----------------- | -----------------
78
+ Ecocash | ecocash
79
+ OneMoney | onemoney
80
+ Paynow | pay_with_paynow
81
+ Visa or Mastercard | vmc
82
+
83
+
84
+
85
+ ## List of mandatory and optional fields when setting up your payments ##
86
+
87
+ #### Basic payments that redirect to Paynow ####
88
+ Mandatory | Optional
89
+ ----------------- | -----------------
90
+ amount | additionalinfo
91
+ method | authemail
92
+ reference | tokenize
93
+
94
+
95
+ #### Ecocash and OneMoney ####
96
+ Mandatory |
97
+ ----------------- |
98
+ authemail |
99
+ amount |
100
+ method |
101
+ phone |
102
+ reference |
103
+
104
+ #### Visa and Mastercard ####
105
+ Mandatory | Optional
106
+ ----------------- | -----------------
107
+ authemail | billingline2
108
+ amount | billingprovince
109
+ billingcity | tokenize
110
+ billingcountry |
111
+ billingline1 |
112
+ cardnumber |
113
+ cardname |
114
+ cardcvv |
115
+ cardexpiry |
116
+ method |
117
+ reference |
118
+ token |
119
+
120
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "test/*_test.rb"
5
+ end
6
+
7
+ desc "Run tests"
8
+ task default: :test
@@ -0,0 +1,13 @@
1
+ module Paynow
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ desc "Create Paynow configuration file"
7
+
8
+ def copy_config
9
+ template 'config/initializers/paynow.rb'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ #Use this hook to configure the parameters necessary for your payments to work
2
+ #Uncomment all the commented configs and use values applicable to your own application
3
+ #Paynow credentials can be obtained from the Paynow website
4
+ #Consider using rails credentials for sensitive secrets
5
+ Paynow.setup do |config|
6
+
7
+ # ==> Configure Paynow Secrets
8
+ #config.integration_id = Rails.application.credentials.paynow[:integration_id]
9
+ #config.integration_key = Rails.application.credentials.paynow[:integration_key]
10
+
11
+ # ==> Configure Paynow Urls
12
+ #config.resulturl = "http://www.worldieweirdos.com/search?q=resulturl"
13
+ #config.returnurl = "http://www.worldieweirdos.com/search?q=returnurl"
14
+
15
+ end
data/lib/paynow.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'paynow/client'
2
+ require 'paynow/configuration'
3
+ require 'paynow/config_dev'
4
+ require 'paynow/errors'
5
+ require 'paynow/hash_generator'
6
+ require 'paynow/payment'
7
+ require 'paynow/payment_attributes'
8
+ require 'paynow/payment_builder'
9
+
10
+
11
+
12
+
@@ -0,0 +1,79 @@
1
+ require 'net/http'
2
+
3
+ module Paynow
4
+ class Client
5
+ attr_reader :body
6
+
7
+ def self.create_payment(body)
8
+ new(body).create_payment
9
+ end
10
+
11
+ def initialize(body)
12
+ @body = body
13
+ end
14
+
15
+ def create_payment
16
+ puts(raw_payment)
17
+ logger.info("[PAYNOW] STARTED POST #{uri}")
18
+
19
+ response = Net::HTTP.post(uri, payment_data,headers)
20
+
21
+ decoded_response = URI.decode_www_form(response.read_body).to_h.transform_keys(&:to_sym)
22
+
23
+ if response.code =~ /^[4-5]/
24
+ logger.error("[PAYNOW] ERROR GET #{uri} status=#{response.code} message=#{decoded_response.error}")
25
+ else
26
+ logger.info("[PAYNOW] COMPLETED GET #{uri} status=#{response.code} response=#{decoded_response}")
27
+ end
28
+
29
+ payment.new(
30
+ raw_payment.merge(
31
+ poll_url: decoded_response[:pollurl],
32
+ paynow_redirect_url: decoded_response[:browserurl],
33
+ status: decoded_response[:status]
34
+ )
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def uri
41
+ if raw_payment.has_key?(:method)
42
+ URI(Paynow::Config.initiate_express_transaction_url)
43
+ else
44
+ URI(Paynow::Config.initiate_transaction_url)
45
+ end
46
+ end
47
+
48
+ def payment_data
49
+ URI.encode_www_form(raw_payment.merge({hash: _hash}))
50
+ end
51
+
52
+ def raw_payment
53
+ payment_builder.build(body)
54
+ end
55
+
56
+ def _hash
57
+ Paynow::HashGenerator._hmac(raw_payment)
58
+ end
59
+
60
+ def headers
61
+ {'content-type': 'application/x-www-form-urlencoded'}
62
+ end
63
+
64
+ def payment
65
+ Paynow::Config.payment
66
+ end
67
+
68
+ def payment_builder
69
+ Paynow::Config.payment_builder
70
+ end
71
+
72
+ def logger
73
+ Paynow::Config.logger
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+
@@ -0,0 +1,6 @@
1
+ Paynow.setup do |config|
2
+ config.integration_id = "11493"
3
+ config.integration_key = "b02e5e6e-7cf4-4c5e-b11c-f3c9090b345b"
4
+ config.resulturl = "http://www.google.com/search?q=resulturl"
5
+ config.returnurl = "http://www.google.com/search?q=returnurl"
6
+ end
@@ -0,0 +1,45 @@
1
+ require "logger"
2
+
3
+ module Paynow
4
+ class << self
5
+ attr_accessor :integration_id,
6
+ :integration_key,
7
+ :resulturl,
8
+ :returnurl
9
+
10
+ def setup(&block)
11
+ instance_eval(&block)
12
+ end
13
+ end
14
+
15
+ class Config
16
+ attr_writer :logger
17
+
18
+ class << self
19
+ def client
20
+ Paynow::Client
21
+ end
22
+
23
+ def logger
24
+ @logger ||= Logger.new($stdout)
25
+ end
26
+
27
+ def payment
28
+ Paynow::Payment
29
+ end
30
+
31
+ def payment_builder
32
+ Paynow::PaymentBuilder
33
+ end
34
+
35
+ def initiate_transaction_url
36
+ "https://www.paynow.co.zw/interface/initiatetransaction/"
37
+ end
38
+
39
+ def initiate_express_transaction_url
40
+ "https://www.paynow.co.zw/interface/remotetransaction/"
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,13 @@
1
+ module Paynow
2
+ class UnknownAttributeError < StandardError
3
+ def initialize(msg="No such attribute. Refer to docs for an applicable attribute")
4
+ super
5
+ end
6
+ end
7
+
8
+ class MissingAttributeError < StandardError
9
+ def initialize(msg="Attribute is missing")
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ require 'digest'
2
+
3
+ module Paynow
4
+ class HashGenerator
5
+ attr_reader :data
6
+
7
+ class << self
8
+ def _hmac(data)
9
+ new(data)._hmac
10
+ end
11
+ end
12
+
13
+ def initialize(data)
14
+ @data = data
15
+ end
16
+
17
+ def _hmac
18
+ return unless data
19
+
20
+ hash_data = concatenate_hash_values_and_append_key(data)
21
+ puts(hash_data)
22
+
23
+ _hash = Digest::SHA512.hexdigest(hash_data).upcase
24
+ end
25
+
26
+ private
27
+
28
+ def concatenate_hash_values_and_append_key(h)
29
+ str = ""
30
+ h.each { |k,v| str += v.to_s }
31
+ str += key
32
+ end
33
+
34
+ def key
35
+ Paynow.integration_key
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ module Paynow
2
+ class Payment
3
+ attr_reader :amount,
4
+ :additionalinfo,
5
+ :authemail,
6
+ :billingline1,
7
+ :billingline2,
8
+ :billingcity,
9
+ :billingprovince,
10
+ :billingcountry,
11
+ :cardnumber,
12
+ :cardname,
13
+ :cardcvv,
14
+ :cardexpiry,
15
+ :id,
16
+ :merchanttrace,
17
+ :method,
18
+ :phone,
19
+ :poll_url,
20
+ :reference,
21
+ :returnurl,
22
+ :resulturl,
23
+ :status,
24
+ :token,
25
+ :tokenize,
26
+
27
+ def self.create(*args)
28
+ Paynow::Config.client.create_payment(*args)
29
+ end
30
+
31
+ def initialize(attrs)
32
+ attrs.each do |k,v|
33
+ variable_name = "@#{k}"
34
+ instance_variable_set(variable_name,v) unless v.nil?
35
+ end
36
+ end
37
+
38
+ def success?
39
+ status == 'Ok'
40
+ end
41
+
42
+ def failed?
43
+ !success?
44
+ end
45
+
46
+ end
47
+ end
48
+
49
+
@@ -0,0 +1,37 @@
1
+ module Paynow
2
+ class PaymentAttributes
3
+ REQUISITE = [
4
+ :amount,
5
+ :reference,
6
+ :method
7
+ ]
8
+
9
+ OPTIONAL = [
10
+ :additionalinfo,
11
+ :authemail,
12
+ :tokenize
13
+ ]
14
+
15
+ CARD = [
16
+ :cardnumber,
17
+ :cardname,
18
+ :cardcvv,
19
+ :cardexpiry,
20
+ :billingline1,
21
+ :billingcity,
22
+ :billingcountry,
23
+ :token,
24
+ :authemail
25
+ ]
26
+
27
+ OPTIONAL_CARD = [
28
+ :billingline2,
29
+ :billingprovince
30
+ ]
31
+
32
+ MOBILE = [
33
+ :phone,
34
+ :authemail
35
+ ]
36
+ end
37
+ end
@@ -0,0 +1,116 @@
1
+ require 'securerandom'
2
+
3
+ module Paynow
4
+ class PaymentBuilder
5
+ def self.build(params)
6
+ new.build(params)
7
+ end
8
+
9
+ def build(attrs)
10
+ payment = { id: id, returnurl: returnurl, resulturl: resulturl, status: "Message"}
11
+
12
+ payment.merge!(
13
+ required_info(attrs),
14
+ optional_info(attrs)
15
+ )
16
+
17
+ payment.tap do |p|
18
+ p.delete(:method) if p[:method] == pay_with_paynow
19
+ p.merge!(card_info(attrs), optional_card_info(attrs)) if p[:method] == visa_or_mastercard
20
+ p.merge!(mobile_info(attrs)) if p[:method] == mobile_money
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ PAYMENT_METHOD_TYPES = %w[ecocash onemoney vmc pay_with_paynow]
27
+
28
+ def id
29
+ Paynow.integration_id
30
+ end
31
+
32
+ def merchanttrace
33
+ SecureRandom.alphanumeric(32)
34
+ end
35
+
36
+ def resulturl
37
+ Paynow.resulturl
38
+ end
39
+
40
+ def returnurl
41
+ Paynow.returnurl
42
+ end
43
+
44
+ def required_info(attrs)
45
+ data = {}
46
+
47
+ Paynow::PaymentAttributes::REQUISITE.each do |attr|
48
+ data.merge!({attr => attrs.fetch(attr)})
49
+ end
50
+
51
+ data.update(amount: sprintf('%.2f',attrs[:amount]),method: payment_method(attrs))
52
+ end
53
+
54
+ def optional_info(attrs)
55
+ data = {}
56
+
57
+ Paynow::PaymentAttributes::OPTIONAL.each do |attr|
58
+ data.merge!({attr => attrs.fetch(attr)}) if attrs.has_key?(attr)
59
+ end
60
+
61
+ data
62
+ end
63
+
64
+ def card_info(attrs)
65
+ data = {}
66
+
67
+ Paynow::PaymentAttributes::CARD.each do |attr|
68
+ data.merge!({attr => attrs.fetch(attr)})
69
+ end
70
+
71
+ data
72
+ end
73
+
74
+ def optional_card_info(attrs)
75
+ data = {}
76
+
77
+ Paynow::PaymentAttributes::OPTIONAL_CARD.each do |attr|
78
+ data.merge!({attr => attrs.fetch(attr)}) if attrs.has_key?(attr)
79
+ end
80
+
81
+ data
82
+ end
83
+
84
+ def mobile_info(attrs)
85
+ data = {}
86
+
87
+ Paynow::PaymentAttributes::MOBILE.each do |attr|
88
+ data.merge!({attr => attrs.fetch(attr)})
89
+ end
90
+
91
+ data
92
+ end
93
+
94
+ def payment_method(attrs)
95
+ if attrs.has_key?(:method) && !PAYMENT_METHOD_TYPES.include?(attrs[:method])
96
+ raise Paynow::UnknownAttributeError, "Payment method should either be ecocash, onemoney, vmc or other"
97
+ elsif !attrs.has_key?(:method)
98
+ raise Paynow::MissingAttributeError, "Payment method is missing"
99
+ else
100
+ attrs.fetch(:method)
101
+ end
102
+ end
103
+
104
+ def mobile_money
105
+ "ecocash" || "onemoney"
106
+ end
107
+
108
+ def visa_or_mastercard
109
+ "vmc"
110
+ end
111
+
112
+ def pay_with_paynow
113
+ "pay_with_paynow"
114
+ end
115
+ end
116
+ end
File without changes
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paynow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bles Muchaya
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-07-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple and slightly opinionated Ruby abstraction for the Paynow API
14
+ in Zimbabwe
15
+ email:
16
+ - btmuchaya@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - lib/generators/paynow/install_generator.rb
25
+ - lib/generators/paynow/templates/config/initializers/paynow.rb
26
+ - lib/paynow.rb
27
+ - lib/paynow/client.rb
28
+ - lib/paynow/config_dev.rb
29
+ - lib/paynow/configuration.rb
30
+ - lib/paynow/errors.rb
31
+ - lib/paynow/hash_generator.rb
32
+ - lib/paynow/payment.rb
33
+ - lib/paynow/payment_attributes.rb
34
+ - lib/paynow/payment_builder.rb
35
+ - lib/paynow/three_d_secure.rb
36
+ homepage: https://github.com/muchaya/paynow
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.4'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.1.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Paynow API wrapper for Ruby on Rails
59
+ test_files: []