malipopay 1.0.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.
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ module Resources
5
+ class Payments
6
+ def initialize(http_client)
7
+ @http = http_client
8
+ end
9
+
10
+ # Initiate a payment (collection or disbursement)
11
+ # @param params [Hash] Payment parameters
12
+ # @return [Hash] Payment response
13
+ def initiate(params)
14
+ @http.post("/api/v1/payment", body: params)
15
+ end
16
+
17
+ # Initiate a mobile money collection
18
+ # @param params [Hash] Collection parameters (amount, phone, provider, reference, etc.)
19
+ # @return [Hash] Collection response
20
+ def collect(params)
21
+ @http.post("/api/v1/payment/collection", body: params)
22
+ end
23
+
24
+ # Initiate a disbursement (send money)
25
+ # @param params [Hash] Disbursement parameters
26
+ # @return [Hash] Disbursement response
27
+ def disburse(params)
28
+ @http.post("/api/v1/payment/disbursement", body: params)
29
+ end
30
+
31
+ # Process an instant payment
32
+ # @param params [Hash] Payment parameters
33
+ # @return [Hash] Payment response
34
+ def pay_now(params)
35
+ @http.post("/api/v1/payment/now", body: params)
36
+ end
37
+
38
+ # Verify a payment by reference
39
+ # @param reference [String] Payment reference
40
+ # @return [Hash] Payment verification details
41
+ def verify(reference)
42
+ @http.get("/api/v1/payment/verify/#{reference}")
43
+ end
44
+
45
+ # Get a payment by reference
46
+ # @param reference [String] Payment reference
47
+ # @return [Hash] Payment details
48
+ def get(reference)
49
+ @http.get("/api/v1/payment/reference/#{reference}")
50
+ end
51
+
52
+ # List all payments
53
+ # @param params [Hash] Query parameters (page, limit, etc.)
54
+ # @return [Hash] Paginated list of payments
55
+ def list(params = {})
56
+ @http.get("/api/v1/payment", params: params)
57
+ end
58
+
59
+ # Search payments
60
+ # @param params [Hash] Search parameters
61
+ # @return [Hash] Search results
62
+ def search(params = {})
63
+ @http.get("/api/v1/payment/search", params: params)
64
+ end
65
+
66
+ # Approve a pending payment
67
+ # @param params [Hash] Approval parameters (reference, etc.)
68
+ # @return [Hash] Approval response
69
+ def approve(params)
70
+ @http.post("/api/v1/payment/approve", body: params)
71
+ end
72
+
73
+ # Retry a failed collection
74
+ # @param reference [String] Payment reference to retry
75
+ # @return [Hash] Retry response
76
+ def retry_collection(reference)
77
+ @http.get("/api/v1/payment/retry/#{reference}")
78
+ end
79
+
80
+ # Create a payment link
81
+ # @param params [Hash] Payment link parameters
82
+ # @return [Hash] Payment link response
83
+ def create_link(params)
84
+ @http.post("/api/v1/pay", body: params)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ module Resources
5
+ class Products
6
+ def initialize(http_client)
7
+ @http = http_client
8
+ end
9
+
10
+ # Create a new product
11
+ # @param params [Hash] Product parameters (name, price, description, etc.)
12
+ # @return [Hash] Created product
13
+ def create(params)
14
+ @http.post("/api/v1/product", body: params)
15
+ end
16
+
17
+ # List all products
18
+ # @param params [Hash] Query parameters (page, limit, etc.)
19
+ # @return [Hash] Paginated list of products
20
+ def list(params = {})
21
+ @http.get("/api/v1/product", params: params)
22
+ end
23
+
24
+ # Get a product by ID
25
+ # @param id [String] Product ID
26
+ # @return [Hash] Product details
27
+ def get(id)
28
+ @http.get("/api/v1/product/#{id}")
29
+ end
30
+
31
+ # Get a product by product number
32
+ # @param number [String] Product number
33
+ # @return [Hash] Product details
34
+ def get_by_number(number)
35
+ @http.get("/api/v1/product", params: { productNumber: number })
36
+ end
37
+
38
+ # Update an existing product
39
+ # @param id [String] Product ID
40
+ # @param params [Hash] Updated product parameters
41
+ # @return [Hash] Updated product
42
+ def update(id, params)
43
+ @http.put("/api/v1/product/#{id}", body: params)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ module Resources
5
+ class References
6
+ def initialize(http_client)
7
+ @http = http_client
8
+ end
9
+
10
+ # List all supported banks
11
+ # @param params [Hash] Query parameters
12
+ # @return [Hash] List of banks
13
+ def banks(params = {})
14
+ @http.get("/api/v1/banks", params: params)
15
+ end
16
+
17
+ # List all supported financial institutions
18
+ # @param params [Hash] Query parameters
19
+ # @return [Hash] List of institutions
20
+ def institutions(params = {})
21
+ @http.get("/api/v1/banks", params: params)
22
+ end
23
+
24
+ # List all supported currencies
25
+ # @param params [Hash] Query parameters
26
+ # @return [Hash] List of currencies
27
+ def currencies(params = {})
28
+ @http.get("/api/v1/currency", params: params)
29
+ end
30
+
31
+ # List all supported countries
32
+ # @param params [Hash] Query parameters
33
+ # @return [Hash] List of countries
34
+ def countries(params = {})
35
+ @http.get("/api/v1/countries", params: params)
36
+ end
37
+
38
+ # List business types
39
+ # @param params [Hash] Query parameters
40
+ # @return [Hash] List of business types
41
+ def business_types(params = {})
42
+ @http.get("/api/v1/countries", params: params.merge(type: "business"))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ module Resources
5
+ class Sms
6
+ def initialize(http_client)
7
+ @http = http_client
8
+ end
9
+
10
+ # Send a single SMS
11
+ # @param params [Hash] SMS parameters (to, message, senderId, etc.)
12
+ # @return [Hash] SMS send response
13
+ def send_sms(params)
14
+ @http.post("/sms/", body: params)
15
+ end
16
+
17
+ # Send bulk SMS
18
+ # @param params [Hash] Bulk SMS parameters (messages array, senderId, etc.)
19
+ # @return [Hash] Bulk SMS response
20
+ def send_bulk(params)
21
+ @http.post("/sms/bulk", body: params)
22
+ end
23
+
24
+ # Schedule an SMS for later delivery
25
+ # @param params [Hash] Schedule parameters (to, message, senderId, scheduledAt, etc.)
26
+ # @return [Hash] Scheduled SMS response
27
+ def schedule(params)
28
+ @http.post("/sms/schedule", body: params)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ module Resources
5
+ class Transactions
6
+ def initialize(http_client)
7
+ @http = http_client
8
+ end
9
+
10
+ # List all transactions
11
+ # @param params [Hash] Query parameters (page, limit, type, status, etc.)
12
+ # @return [Hash] Paginated list of transactions
13
+ def list(params = {})
14
+ @http.get("/api/v1/transactions", params: params)
15
+ end
16
+
17
+ # Get a transaction by ID
18
+ # @param id [String] Transaction ID
19
+ # @return [Hash] Transaction details
20
+ def get(id)
21
+ @http.get("/api/v1/transactions/#{id}")
22
+ end
23
+
24
+ # Search transactions
25
+ # @param params [Hash] Search parameters (query, dateFrom, dateTo, etc.)
26
+ # @return [Hash] Search results
27
+ def search(params = {})
28
+ @http.get("/api/v1/transactions/search", params: params)
29
+ end
30
+
31
+ # Paginate through transactions
32
+ # @param params [Hash] Pagination parameters (page, limit)
33
+ # @yield [Hash] Each page of transactions
34
+ # @return [Enumerator] If no block given
35
+ def paginate(params = {})
36
+ return enum_for(:paginate, params) unless block_given?
37
+
38
+ page = params.fetch(:page, 1)
39
+ loop do
40
+ result = list(params.merge(page: page))
41
+ yield result
42
+
43
+ records = result["data"] || result["records"] || []
44
+ break if records.empty?
45
+
46
+ page += 1
47
+ end
48
+ end
49
+
50
+ # Get tariff information
51
+ # @param params [Hash] Query parameters
52
+ # @return [Hash] Tariff details
53
+ def tariffs(params = {})
54
+ @http.get("/api/v1/transactions", params: params.merge(type: "tariff"))
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaliPoPay
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "json"
5
+
6
+ module MaliPoPay
7
+ module Webhooks
8
+ class Verifier
9
+ TOLERANCE_IN_SECONDS = 300 # 5 minutes
10
+
11
+ def initialize(secret)
12
+ @secret = secret
13
+ end
14
+
15
+ # Verify a webhook signature
16
+ # @param payload [String] Raw request body
17
+ # @param signature [String] Signature from X-MaliPoPay-Signature header
18
+ # @param timestamp [String, nil] Timestamp from X-MaliPoPay-Timestamp header
19
+ # @return [Boolean] Whether the signature is valid
20
+ def verify(payload, signature, timestamp: nil)
21
+ return false if signature.nil? || signature.empty?
22
+
23
+ if timestamp
24
+ ts = timestamp.to_i
25
+ return false if (Time.now.to_i - ts).abs > TOLERANCE_IN_SECONDS
26
+ end
27
+
28
+ expected = self.class.sign(payload, @secret, timestamp: timestamp)
29
+ secure_compare(expected, signature)
30
+ end
31
+
32
+ # Verify and parse a webhook event
33
+ # @param payload [String] Raw request body
34
+ # @param signature [String] Signature from header
35
+ # @param timestamp [String, nil] Timestamp from header
36
+ # @return [Hash] Parsed event data
37
+ # @raise [MaliPoPay::Error] If signature is invalid
38
+ def construct_event(payload, signature, timestamp: nil)
39
+ unless verify(payload, signature, timestamp: timestamp)
40
+ raise MaliPoPay::AuthenticationError.new("Invalid webhook signature")
41
+ end
42
+
43
+ JSON.parse(payload)
44
+ end
45
+
46
+ # Generate an HMAC signature for a payload
47
+ # @param payload [String] Raw payload string
48
+ # @param secret [String] Webhook secret
49
+ # @param timestamp [String, nil] Optional timestamp to include in signature
50
+ # @return [String] Hex-encoded HMAC-SHA256 signature
51
+ def self.sign(payload, secret, timestamp: nil)
52
+ signed_payload = timestamp ? "#{timestamp}.#{payload}" : payload
53
+ OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
54
+ end
55
+
56
+ private
57
+
58
+ def secure_compare(a, b)
59
+ return false unless a.bytesize == b.bytesize
60
+
61
+ l = a.unpack("C*")
62
+ r = b.unpack("C*")
63
+ result = 0
64
+ l.zip(r) { |x, y| result |= x ^ y }
65
+ result.zero?
66
+ end
67
+ end
68
+ end
69
+ end
data/lib/malipopay.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "malipopay/version"
4
+ require_relative "malipopay/errors"
5
+ require_relative "malipopay/http_client"
6
+ require_relative "malipopay/client"
7
+
8
+ # Resources
9
+ require_relative "malipopay/resources/payments"
10
+ require_relative "malipopay/resources/customers"
11
+ require_relative "malipopay/resources/invoices"
12
+ require_relative "malipopay/resources/products"
13
+ require_relative "malipopay/resources/transactions"
14
+ require_relative "malipopay/resources/account"
15
+ require_relative "malipopay/resources/sms"
16
+ require_relative "malipopay/resources/references"
17
+
18
+ # Webhooks
19
+ require_relative "malipopay/webhooks/verifier"
20
+
21
+ module MaliPoPay
22
+ # Convenience method to create a new client
23
+ #
24
+ # @param options [Hash] Options passed to MaliPoPay::Client.new
25
+ # @return [MaliPoPay::Client]
26
+ def self.client(**options)
27
+ Client.new(**options)
28
+ end
29
+ end
data/malipopay.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/malipopay/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "malipopay"
7
+ spec.version = MaliPoPay::VERSION
8
+ spec.authors = ["Lockwood Technology Ltd"]
9
+ spec.email = ["developers@malipopay.co.tz"]
10
+
11
+ spec.summary = "Official Ruby SDK for the MaliPoPay payment platform"
12
+ spec.description = "Ruby client library for integrating with MaliPoPay payment APIs. " \
13
+ "Supports mobile money collections, disbursements, invoicing, " \
14
+ "SMS, customer management, and more."
15
+ spec.homepage = "https://github.com/malipopay/malipopay-ruby"
16
+ spec.license = "MIT"
17
+
18
+ spec.required_ruby_version = ">= 3.0.0"
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = spec.homepage
22
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
+ spec.metadata["documentation_uri"] = "https://docs.malipopay.co.tz"
24
+
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?("spec/", "examples/", ".git", ".github")
29
+ end
30
+ end
31
+
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.add_dependency "faraday", "~> 2.0"
35
+ spec.add_dependency "faraday-retry", "~> 2.0"
36
+
37
+ spec.add_development_dependency "bundler", "~> 2.0"
38
+ spec.add_development_dependency "rake", "~> 13.0"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ spec.add_development_dependency "webmock", "~> 3.0"
41
+ spec.add_development_dependency "rubocop", "~> 1.0"
42
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: malipopay
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Lockwood Technology Ltd
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-retry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
111
+ description: Ruby client library for integrating with MaliPoPay payment APIs. Supports
112
+ mobile money collections, disbursements, invoicing, SMS, customer management, and
113
+ more.
114
+ email:
115
+ - developers@malipopay.co.tz
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - Gemfile
121
+ - LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - docs/configuration.md
125
+ - docs/customers.md
126
+ - docs/error-handling.md
127
+ - docs/getting-started.md
128
+ - docs/invoices.md
129
+ - docs/payments.md
130
+ - docs/sms.md
131
+ - docs/webhooks.md
132
+ - lib/malipopay.rb
133
+ - lib/malipopay/client.rb
134
+ - lib/malipopay/errors.rb
135
+ - lib/malipopay/http_client.rb
136
+ - lib/malipopay/resources/account.rb
137
+ - lib/malipopay/resources/customers.rb
138
+ - lib/malipopay/resources/invoices.rb
139
+ - lib/malipopay/resources/payments.rb
140
+ - lib/malipopay/resources/products.rb
141
+ - lib/malipopay/resources/references.rb
142
+ - lib/malipopay/resources/sms.rb
143
+ - lib/malipopay/resources/transactions.rb
144
+ - lib/malipopay/version.rb
145
+ - lib/malipopay/webhooks/verifier.rb
146
+ - malipopay.gemspec
147
+ homepage: https://github.com/malipopay/malipopay-ruby
148
+ licenses:
149
+ - MIT
150
+ metadata:
151
+ homepage_uri: https://github.com/malipopay/malipopay-ruby
152
+ source_code_uri: https://github.com/malipopay/malipopay-ruby
153
+ changelog_uri: https://github.com/malipopay/malipopay-ruby/blob/main/CHANGELOG.md
154
+ documentation_uri: https://docs.malipopay.co.tz
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 3.0.0
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.0.3.1
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Official Ruby SDK for the MaliPoPay payment platform
174
+ test_files: []