cmi_gateway 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: 5c97198a524328f6200fc9b97710753837cb1b714ce85a1fa63edee8218c529d
4
+ data.tar.gz: 34b389efec7b6f48e6a5a451fdd0882cc36d52505d2528a4ee766d9baf32242e
5
+ SHA512:
6
+ metadata.gz: 04c936c4c22cce98adfc2ee290db15f46f1e474f277f4fce47e87777ce8e736595b7d1d709bb3b5bcc4f59f57d428c1e96a6b0f00be25260190ae3e975449013
7
+ data.tar.gz: 47b3153a671f1c6d34e5f69d253bdfc1413ec6fbb2f9dc1b1a4041a5ebb3ee94e73cd3c5facd40b5e8983b738ee36b3c7f432eb4be80feaa400233ddfe9c4a8a
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-04-07
11
+
12
+ ### Added
13
+
14
+ - `CmiGateway::Configuration` and global `CmiGateway.configure` for environment and merchant profiles (default + named, e.g. `:vc`).
15
+ - `CmiGateway::Checkout` — 3D Pay Hosting parameter build, SHA-512 hash (`ver3`), test/production gateway URLs.
16
+ - `CmiGateway::Callback` — parse callback params; `success?` from `ProcReturnCode` / `Response`.
17
+ - `CmiGateway::Helpers` — amount formatting, hash escaping, transliteration helpers.
18
+ - RSpec suite and GitHub Actions CI (Ruby 3.1–3.3).
19
+
20
+ [Unreleased]: https://github.com/merouaneamqor/cmi_gateway/compare/v0.1.0...HEAD
21
+ [0.1.0]: https://github.com/merouaneamqor/cmi_gateway/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Ollazen
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # cmi_gateway
2
+
3
+ Ruby helpers for **CMI (Centre Monétique Interbancaire)** [3D Pay Hosting](https://www.cmi.co.ma/) integration: build signed checkout form parameters (SHA-512 + Base64) and parse server callbacks.
4
+
5
+ - **No Rails required** — uses only the Ruby standard library.
6
+ - **Multiple merchant profiles** (e.g. default + VC) via `CmiGateway.configure`.
7
+
8
+ ## Installation
9
+
10
+ Add to your `Gemfile`:
11
+
12
+ ```ruby
13
+ gem "cmi_gateway", github: "merouaneamqor/cmi_gateway"
14
+ # or, during development:
15
+ # gem "cmi_gateway", path: "../cmi_gateway"
16
+ ```
17
+
18
+ Then:
19
+
20
+ ```bash
21
+ bundle install
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ ```ruby
27
+ require "cmi_gateway"
28
+
29
+ CmiGateway.configure do |config|
30
+ config.environment = :test # or :production
31
+
32
+ config.client_id = ENV.fetch("CMI_CLIENT_ID", nil)
33
+ config.store_key = ENV.fetch("CMI_STORE_KEY", nil)
34
+
35
+ vc_id = ENV["CMI_VC_CLIENT_ID"].to_s.strip
36
+ vc_key = ENV["CMI_VC_STORE_KEY"].to_s.strip
37
+ if !vc_id.empty? && !vc_key.empty?
38
+ config.add_profile(:vc, client_id: vc_id, store_key: vc_key)
39
+ end
40
+ end
41
+ ```
42
+
43
+ In Rails, put this in `config/initializers/cmi_gateway.rb` and map `config.environment` to `Rails.env.production?`.
44
+
45
+ ## Building a checkout (browser POST to CMI)
46
+
47
+ ```ruby
48
+ checkout = CmiGateway::Checkout.new(
49
+ amount: 199.00,
50
+ order_id: "BOOK-123",
51
+ ok_url: "https://example.com/pay/ok",
52
+ fail_url: "https://example.com/pay/fail",
53
+ callback_url: "https://api.example.com/webhooks/cmi",
54
+ email: "user@example.com",
55
+ phone: "0600000000",
56
+ bill_to_name: "Jean Dupont",
57
+ bill_to_street1: "12 Rue Example",
58
+ bill_to_city: "Casablanca",
59
+ bill_to_postal_code: "20000",
60
+ shopurl: "https://example.com",
61
+ lang: "fr",
62
+ tran_type: "PreAuth",
63
+ profile: :default, # or :vc when configured
64
+ accent_strip: false, # set true for stricter VC-style accent handling on hash inputs
65
+ extra_params: {} # merged into signed params (string keys recommended)
66
+ )
67
+
68
+ checkout.action_url # test or production est3Dgate URL
69
+ checkout.params # Hash including "hash" — use as hidden fields in a form POST
70
+ ```
71
+
72
+ Validate before rendering:
73
+
74
+ ```ruby
75
+ checkout.validate! # raises CmiGateway::ValidationError
76
+ # or
77
+ checkout.valid?
78
+ checkout.errors
79
+ ```
80
+
81
+ ### Low-level hash (tests / custom param sets)
82
+
83
+ ```ruby
84
+ params_without_hash = { "clientid" => "...", "amount" => "10.00", "encoding" => "utf-8" }
85
+ digest = CmiGateway::Checkout.build_hash(params_without_hash, "store_key", accent_strip: false)
86
+ ```
87
+
88
+ ## Parsing callbacks
89
+
90
+ CMI posts form fields back to your `callbackUrl`. Success is detected the same way as common CMI samples: `ProcReturnCode == "00"` or `Response` case-insensitively equals `"Approved"`.
91
+
92
+ ```ruby
93
+ callback = CmiGateway::Callback.new(request.request_parameters) # Rails
94
+ # or CmiGateway::Callback.new(params.to_unsafe_h)
95
+
96
+ callback.success?
97
+ callback.order_id
98
+ callback.transaction_id
99
+ callback.auth_code
100
+ callback.error_code # when not successful
101
+ callback.error_message
102
+ callback.raw # indifferent string-key hash
103
+ ```
104
+
105
+ **Security note:** This gem does not verify a callback `hash` from CMI; confirm with your acquirer whether server-to-server verification is required and implement it if so.
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ bundle install
111
+ bundle exec rspec
112
+ bundle exec rubocop
113
+ ```
114
+
115
+ ## Community
116
+
117
+ - [Contributing](CONTRIBUTING.md)
118
+ - [Code of conduct](CODE_OF_CONDUCT.md)
119
+ - [Security policy](SECURITY.md)
120
+ - [Changelog](CHANGELOG.md)
121
+ - AI assistants: see [CLAUDE.md](CLAUDE.md)
122
+
123
+ ## License
124
+
125
+ MIT — see [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ class Callback
5
+ attr_reader :raw
6
+
7
+ def initialize(params)
8
+ @raw = stringify_keys(params)
9
+ end
10
+
11
+ def success?
12
+ code = @raw["ProcReturnCode"].to_s
13
+ response = @raw["Response"].to_s
14
+ code == "00" || response.casecmp("Approved").zero?
15
+ end
16
+
17
+ def order_id
18
+ o = @raw["oid"].to_s.strip
19
+ return o unless o.empty?
20
+
21
+ o2 = @raw["OrderId"].to_s.strip
22
+ o2.empty? ? nil : o2
23
+ end
24
+
25
+ def transaction_id
26
+ t = @raw["TransId"].to_s.strip
27
+ t.empty? ? nil : t
28
+ end
29
+
30
+ def auth_code
31
+ a = @raw["AuthCode"].to_s.strip
32
+ a.empty? ? nil : a
33
+ end
34
+
35
+ def error_code
36
+ return nil if success?
37
+
38
+ c = @raw["ProcReturnCode"].to_s.strip
39
+ c.empty? ? nil : c
40
+ end
41
+
42
+ def error_message
43
+ return nil if success?
44
+
45
+ m = @raw["ErrMsg"].to_s.strip
46
+ m.empty? ? nil : m
47
+ end
48
+
49
+ def [](key)
50
+ @raw[key.to_s]
51
+ end
52
+
53
+ private
54
+
55
+ def stringify_keys(params)
56
+ return {} if params.nil?
57
+
58
+ params.each_with_object({}) do |(k, v), out|
59
+ out[k.to_s] = v
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "base64"
5
+ require "bigdecimal"
6
+
7
+ module CmiGateway
8
+ class Checkout
9
+ STORE_TYPE = "3D_PAY_HOSTING"
10
+ DEFAULT_HASH_ALGORITHM = "ver3"
11
+ DEFAULT_ENCODING = "utf-8"
12
+ DEFAULT_LANG = "fr"
13
+ DEFAULT_TRAN_TYPE = "PreAuth"
14
+ DEFAULT_CURRENCY = "504"
15
+ DEFAULT_COUNTRY = "504"
16
+
17
+ PRODUCTION_URL = "https://payment.cmi.co.ma/fim/est3Dgate"
18
+ TEST_URL = "https://testpayment.cmi.co.ma/fim/est3Dgate"
19
+
20
+ attr_reader :accent_strip, :profile_name
21
+
22
+ def initialize(
23
+ amount:,
24
+ order_id:,
25
+ ok_url:,
26
+ fail_url:,
27
+ callback_url:,
28
+ currency: DEFAULT_CURRENCY,
29
+ lang: DEFAULT_LANG,
30
+ tran_type: DEFAULT_TRAN_TYPE,
31
+ email: nil,
32
+ phone: nil,
33
+ bill_to_name: nil,
34
+ bill_to_street1: nil,
35
+ bill_to_city: nil,
36
+ bill_to_postal_code: nil,
37
+ bill_to_country: DEFAULT_COUNTRY,
38
+ shopurl: nil,
39
+ accent_strip: false,
40
+ profile: :default,
41
+ extra_params: {},
42
+ configuration: nil
43
+ )
44
+ @amount = amount
45
+ @order_id = order_id
46
+ @ok_url = ok_url
47
+ @fail_url = fail_url
48
+ @callback_url = callback_url
49
+ @currency = currency
50
+ @lang = lang
51
+ @tran_type = tran_type
52
+ @email = email
53
+ @phone = phone
54
+ @bill_to_name = bill_to_name
55
+ @bill_to_street1 = bill_to_street1
56
+ @bill_to_city = bill_to_city
57
+ @bill_to_postal_code = bill_to_postal_code
58
+ @bill_to_country = bill_to_country
59
+ @shopurl = shopurl
60
+ @accent_strip = accent_strip
61
+ @profile_name = profile
62
+ @extra_params = stringify_keys(extra_params || {})
63
+ @configuration = configuration || CmiGateway.configuration
64
+ end
65
+
66
+ def action_url
67
+ @configuration.production? ? PRODUCTION_URL : TEST_URL
68
+ end
69
+
70
+ def params
71
+ @params ||= build_signed_params
72
+ end
73
+
74
+ def valid?
75
+ errors.empty?
76
+ end
77
+
78
+ def validate!
79
+ raise ValidationError, errors.join(", ") unless valid?
80
+
81
+ self
82
+ end
83
+
84
+ def errors
85
+ @errors ||= begin
86
+ errs = []
87
+ errs << "amount is required" if @amount.nil? && !@extra_params.key?("amount")
88
+ errs << "order_id is required" if @order_id.to_s.strip.empty?
89
+ errs << "ok_url is required" if @ok_url.to_s.strip.empty?
90
+ errs << "fail_url is required" if @fail_url.to_s.strip.empty?
91
+ errs << "callback_url is required" if @callback_url.to_s.strip.empty?
92
+ begin
93
+ prof = @configuration.profile_for(@profile_name)
94
+ errs << "CMI client_id is missing for profile #{@profile_name.inspect}" if prof.client_id.to_s.strip.empty?
95
+ errs << "CMI store_key is missing for profile #{@profile_name.inspect}" if prof.store_key.to_s.strip.empty?
96
+ rescue UnknownProfileError => e
97
+ errs << e.message
98
+ end
99
+ errs
100
+ end
101
+ end
102
+
103
+ def self.build_hash(params, store_key, accent_strip: false)
104
+ plain = +""
105
+ params.keys.sort_by(&:downcase).each do |key|
106
+ next if %w[hash encoding].include?(key)
107
+
108
+ val = params[key].to_s.strip
109
+ val = val.tr(Helpers::ACCENT_MAP, Helpers::ACCENT_REP) if accent_strip
110
+ plain << Helpers.escape_hash_component(val) << "|"
111
+ end
112
+ plain << store_key.to_s
113
+
114
+ Base64.strict_encode64(Digest::SHA2.new(512).digest(plain))
115
+ end
116
+
117
+ private
118
+
119
+ def profile
120
+ @profile ||= @configuration.profile_for(@profile_name)
121
+ end
122
+
123
+ def build_signed_params
124
+ validate!
125
+
126
+ amount_value = @amount.nil? ? @extra_params["amount"] : @amount
127
+
128
+ base = {
129
+ "clientid" => profile.client_id.to_s,
130
+ "storetype" => STORE_TYPE,
131
+ "TranType" => @tran_type.to_s,
132
+ "amount" => Helpers.format_amount(amount_value),
133
+ "currency" => @currency.to_s,
134
+ "oid" => @order_id.to_s,
135
+ "okUrl" => @ok_url.to_s,
136
+ "failUrl" => @fail_url.to_s,
137
+ "callbackUrl" => @callback_url.to_s,
138
+ "lang" => @lang.to_s,
139
+ "rnd" => random_rnd,
140
+ "hashAlgorithm" => DEFAULT_HASH_ALGORITHM,
141
+ "encoding" => DEFAULT_ENCODING,
142
+ "callBackResponse" => "true",
143
+ }
144
+
145
+ base["email"] = @email.to_s if @email
146
+ base["tel"] = @phone.to_s if @phone
147
+ base["BillToName"] = format_name(@bill_to_name) if @bill_to_name
148
+ base["BillToStreet1"] = format_street(@bill_to_street1) if @bill_to_street1
149
+ base["BillToCity"] = @bill_to_city.to_s.strip if @bill_to_city
150
+ base["BillToPostalCode"] = @bill_to_postal_code.to_s if @bill_to_postal_code
151
+ base["BillToCountry"] = @bill_to_country.to_s
152
+ base["shopurl"] = @shopurl.to_s if @shopurl
153
+
154
+ merged = base.merge(@extra_params)
155
+ hash = self.class.build_hash(merged, profile.store_key, accent_strip: @accent_strip)
156
+ merged.merge("hash" => hash)
157
+ end
158
+
159
+ def random_rnd
160
+ rand(36**17).to_s(36)
161
+ end
162
+
163
+ def format_name(name)
164
+ base = name.to_s.strip
165
+ return "" if base.empty?
166
+
167
+ ascii = base.unicode_normalize(:nfkd).encode("ASCII", invalid: :replace, undef: :replace, replace: "")
168
+ ascii = Helpers.apply_accent_map(ascii) if @accent_strip
169
+ ascii.strip
170
+ end
171
+
172
+ def format_street(address)
173
+ base = Helpers.transliterate_street(address)
174
+ base = Helpers.apply_accent_map(base) if @accent_strip
175
+ base
176
+ end
177
+
178
+ def stringify_keys(hash)
179
+ hash.each_with_object({}) do |(k, v), out|
180
+ out[k.to_s] = v
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ class Configuration
5
+ attr_accessor :environment, :client_id, :store_key
6
+ attr_reader :named_profiles
7
+
8
+ def initialize
9
+ @environment = :test
10
+ @client_id = nil
11
+ @store_key = nil
12
+ @named_profiles = {}
13
+ end
14
+
15
+ def add_profile(name, client_id:, store_key:)
16
+ @named_profiles[name.to_sym] = Profile.new(client_id: client_id, store_key: store_key)
17
+ end
18
+
19
+ def profile_for(name)
20
+ key = (name || :default).to_sym
21
+ return default_profile if key == :default
22
+
23
+ prof = @named_profiles[key]
24
+ raise UnknownProfileError, "Unknown CMI profile: #{name.inspect}" if prof.nil?
25
+
26
+ prof
27
+ end
28
+
29
+ def default_profile
30
+ Profile.new(client_id: @client_id, store_key: @store_key)
31
+ end
32
+
33
+ def production?
34
+ @environment.to_s.downcase == "production"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ class Error < StandardError; end
5
+
6
+ class ConfigurationError < Error; end
7
+
8
+ class UnknownProfileError < Error; end
9
+
10
+ class ValidationError < Error; end
11
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ module Helpers
5
+ module_function
6
+
7
+ ACCENT_MAP = "àâçèéêîôùû"
8
+ ACCENT_REP = "aaceeeiouu"
9
+
10
+ # CMI amount format: two decimal places.
11
+ def format_amount(value)
12
+ numeric = case value
13
+ when String then BigDecimal(value)
14
+ when Integer, Float, BigDecimal then BigDecimal(value.to_s)
15
+ else BigDecimal("0")
16
+ end
17
+ format("%.2f", numeric)
18
+ end
19
+
20
+ # Hash pipeline: strip, escape | then \ (order matches CMI integration gist).
21
+ def escape_hash_component(value)
22
+ value.to_s.strip.gsub("|", "\\|").gsub("\\", "\\\\")
23
+ end
24
+
25
+ def apply_accent_map(value)
26
+ value.to_s.tr(ACCENT_MAP, ACCENT_REP)
27
+ end
28
+
29
+ # ASCII-ish name for BillTo fields (no Rails I18n).
30
+ def transliterate_name(name)
31
+ base = name.to_s.strip
32
+ return "" if base.empty?
33
+
34
+ base.unicode_normalize(:nfkd).encode("ASCII", invalid: :replace, undef: :replace, replace: "")
35
+ .gsub(/[^0-9A-Za-z ]/, " ")
36
+ .squeeze(" ")
37
+ .strip
38
+ end
39
+
40
+ # Street-style sanitization: transliterate then strip non-alphanumeric except space.
41
+ def transliterate_street(address)
42
+ base = address.to_s.strip
43
+ return "" if base.empty?
44
+
45
+ base.unicode_normalize(:nfkd).encode("ASCII", invalid: :replace, undef: :replace, replace: "")
46
+ .gsub(/[^0-9A-Za-z ]/, "")
47
+ .squeeze(" ")
48
+ .strip
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ class Profile
5
+ attr_reader :client_id, :store_key
6
+
7
+ def initialize(client_id:, store_key:)
8
+ @client_id = client_id
9
+ @store_key = store_key
10
+ end
11
+
12
+ def complete?
13
+ !client_id.to_s.strip.empty? && !store_key.to_s.strip.empty?
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CmiGateway
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cmi_gateway/version"
4
+ require_relative "cmi_gateway/errors"
5
+ require_relative "cmi_gateway/profile"
6
+ require_relative "cmi_gateway/configuration"
7
+ require_relative "cmi_gateway/helpers"
8
+ require_relative "cmi_gateway/checkout"
9
+ require_relative "cmi_gateway/callback"
10
+
11
+ module CmiGateway
12
+ class << self
13
+ def configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def configure
18
+ yield configuration
19
+ end
20
+
21
+ def reset_configuration!
22
+ @configuration = Configuration.new
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cmi_gateway
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - AMQOR MEROUANE
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.60'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.60'
55
+ description: Builds CMI payment form parameters and SHA-512 hash; parses server callbacks.
56
+ email:
57
+ - marouaneamqor@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CHANGELOG.md
63
+ - LICENSE.txt
64
+ - README.md
65
+ - lib/cmi_gateway.rb
66
+ - lib/cmi_gateway/callback.rb
67
+ - lib/cmi_gateway/checkout.rb
68
+ - lib/cmi_gateway/configuration.rb
69
+ - lib/cmi_gateway/errors.rb
70
+ - lib/cmi_gateway/helpers.rb
71
+ - lib/cmi_gateway/profile.rb
72
+ - lib/cmi_gateway/version.rb
73
+ homepage: https://github.com/merouaneamqor/cmi_gateway
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/merouaneamqor/cmi_gateway
78
+ source_code_uri: https://github.com/merouaneamqor/cmi_gateway
79
+ changelog_uri: https://github.com/merouaneamqor/cmi_gateway/blob/master/CHANGELOG.md
80
+ bug_tracker_uri: https://github.com/merouaneamqor/cmi_gateway/issues
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.4.19
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: CMI (Morocco) 3D Pay Hosting checkout signing and callback parsing
100
+ test_files: []