MVola 0.1.0.pre.alpha → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 175f60fb58a7232acd1d5a8e77e05aaddfab2c062b52804471a2cb6379e44442
4
- data.tar.gz: feb6bc42a8077d82e4e6963ed5882e138a33ae942b2694153b9e6a0f8c1f583c
3
+ metadata.gz: 72e1eea295a497cfdee69069b02954183fff602200390fa827a1c119b7bcefd6
4
+ data.tar.gz: 422ad6db333e294ed0ea1796d59683535c90d6dd3eb71308e19cc925f670a43c
5
5
  SHA512:
6
- metadata.gz: 8e6a383f391c4ecf506d1be02c835839e7fedd4999c7c85d4e13f69efe374b61db122c814c9196f1809c9e8f15480bd8517ffe8b174a0cb123cdb684da39b992
7
- data.tar.gz: be28e94b14a83607e75f0fa5e2e420b28282fbbc1d0083100b1b27ed44030658f7ea9e3c71d857cd0b30c88d9e75d9cc7ade60d8b554e0379d8d21a4901b038a
6
+ metadata.gz: bedbf83485b06400101aac8fbc8c1216d0e210dab9ef611cd9cfa7249c7b83ee3235f6531c8c27f9b4006e1548bcc3dc591f6f9efb47cca72efe3884dba5aca2
7
+ data.tar.gz: f4f15ab163580fc2ab18db25679d405320602e5f1b17d2921632831b3d219edc96d87b623aee059d0edd1f6b73b7ab4b4e48d034fd740dd65baa8738f1651f4c
data/CHANGELOG.md CHANGED
@@ -1,4 +1,6 @@
1
1
  ## [Unreleased]
2
+ ## [1.0.0-alpha] - 2025-02-24
3
+ ## [0.2.0-alpha] - 2025-02-24
2
4
  ## [0.1.0-alpha] - 2025-01-03
3
5
 
4
6
  ## [0.1.0] - 2024-12-30
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ![Build Status](https://github.com/Ksm125/mvola/actions/workflows/ruby.yml/badge.svg)
4
4
 
5
+ <img width="739" alt="Screenshot 2025-01-03 at 12 29 10" src="https://github.com/user-attachments/assets/2cb4f056-cf50-48df-a88b-7797bb289719" />
6
+
7
+
5
8
  MVola is a Ruby gem that provides a simple way to interact with the [Mvola Payment API](https://mvola.mg/).
6
9
 
7
10
  ## Installation
@@ -9,13 +12,13 @@ MVola is a Ruby gem that provides a simple way to interact with the [Mvola Payme
9
12
  Install the gem and add to the application's Gemfile by executing:
10
13
 
11
14
  ```bash
12
- bundle add mvola
15
+ bundle add MVola
13
16
  ```
14
17
 
15
18
  If bundler is not being used to manage dependencies, install the gem by executing:
16
19
 
17
20
  ```bash
18
- gem install mvola
21
+ gem install MVola
19
22
  ```
20
23
 
21
24
  ## Usage
@@ -42,7 +45,9 @@ client.token!
42
45
 
43
46
  #### Tips
44
47
 
45
- We suggest that you store the token in cache system (like `Redis`) and passes the value directly into the client to avoid requesting a new token each time, across multiple instances of the application:
48
+ We suggest that you store the token in cache system (like `Redis`, `Memcached`) and passes the value directly
49
+ into the client to avoid requesting a new token each time, across multiple instances of the application:
50
+
46
51
  ```ruby
47
52
  token = client.token
48
53
  # Store the value in cache
@@ -50,11 +55,109 @@ cache.write('mvola_token', token.to_h)
50
55
 
51
56
  # then next time, you can use the value from cache
52
57
  token = cache.read('mvola_token')
58
+
53
59
  client = MVola::Client.new(consumer_key: 'your_consumer_key', consumer_secret: 'your_consumer_secret', token: token)
54
60
 
55
- client.token # will use the provided token if it's still valid/not expired
61
+ # If the token is still valid, the client will use it directly without requesting a new one.
62
+ # If it's expired, the client will automatically request a new one to make a request.
63
+ client.token
56
64
  ```
57
65
 
66
+ ### Initialize a Payment
67
+
68
+ To initialize a payment, use the `MVola::Client` class and call the `init_payment!` method
69
+
70
+ ```ruby
71
+ ENV["MVOLA_CONSUMER_KEY"] = "CONSUMER_KEY"
72
+ ENV["MVOLA_CONSUMER_SECRET"] = "CONSUMER_SECRET"
73
+ ENV["MVOLA_PARTNER_NAME"] = "PARTNER_NAME"
74
+
75
+ debit_phone_number = "0343500003"
76
+ credit_phone_number = "0343500004"
77
+
78
+ client = MVola::Client.new(sandbox: true)
79
+
80
+ transaction = MVola::Transaction.new(client: client)
81
+ # OR (if you want to use the default client)
82
+ # transaction = MVola::Transaction.new
83
+
84
+ client_transaction_reference = SecureRandom.hex
85
+ # Initiate a payment
86
+ response = transaction.initiate_payment!(amount: 2000,
87
+ description: "attempting to make a test payment",
88
+ debit_phone_number: debit_phone_number,
89
+ credit_phone_number: credit_phone_number,
90
+ client_transaction_reference: client_transaction_reference)
91
+
92
+ pp response
93
+ # => => #<MVola::Transaction::Status:0x000000010361ff90 @client_correlation_id="a029c8fa-d08f-4fd6-a62b-b53e419b3abe", @raw_response={"status"=>"pending", "serverCorrelationId"=>"3d894c69-d6c7-4ed7-8387-95351fb79657", "notificationMethod"=>"polling"}>
94
+
95
+ # Get the status of the payment
96
+ status_response = transaction.get_status(response.server_correlation_id)
97
+
98
+ pp status_response
99
+ # => => #<MVola::Transaction::Status:0x0000000103e51df8 @client_correlation_id="d6670863-b5a8-4998-8211-47c49498b466", @raw_response={"status"=>"completed", "serverCorrelationId"=>"3d894c69-d6c7-4ed7-8387-95351fb79657", "notificationMethod"=>"polling", "objectReference"=>"653815820"}>
100
+
101
+ # transaction_reference is the same as objectReference
102
+ details_response = transaction.get_details(status_response.transaction_reference)
103
+
104
+ pp details_response
105
+ # => #<MVola::Transaction::Details:0x0000000103cc0070
106
+ # @client_correlation_id="44f4da6c-3844-4cb7-a563-bab3cce396a6",
107
+ # @raw_response=
108
+ # {"amount"=>"2000.00",
109
+ # "currency"=>"Ar",
110
+ # "request_date"=>"2025-02-24T18:16:27.674Z",
111
+ # "debit_party"=>[{"key"=>"msisdn", "value"=>"0343500003"}],
112
+ # "credit_party"=>[{"key"=>"msisdn", "value"=>"0343500004"}],
113
+ # "fees"=>[{"fee_amount"=>"0"}],
114
+ # "metadata"=>[{"key"=>"originalTransactionResult", "value"=>"0"}, {"key"=>"originalTransactionResultDesc", "value"=>"0"}],
115
+ # "transaction_status"=>"completed",
116
+ # "creation_date"=>"2025-02-24T15:14:07.810Z",
117
+ # "transaction_reference"=>"653815826"}>
118
+
119
+ ```
120
+
121
+ <details>
122
+
123
+ <summary>Transaction Status and Details Structures</summary>
124
+
125
+ The `MVola::Transaction::Status` and `MVola::Transaction::Details` classes provide detailed information about transactions and their statuses.
126
+
127
+ ### MVola::Transaction::Status
128
+
129
+ The `MVola::Transaction::Status` class provides information about the status of a transaction.
130
+
131
+ It includes the following attributes:
132
+
133
+ - `client_correlation_id`: A unique identifier for the client request.
134
+ - `raw_response`: The raw response from the MVola API, which includes:
135
+ - `status`: The status of the transaction ("pending", "completed", "failed").
136
+ - `serverCorrelationId`: A unique identifier for the server request.
137
+ - `notificationMethod`: The method used for notifications (e.g., "polling").
138
+ - `objectReference`: A reference to the transaction object.
139
+
140
+ ### MVola::Transaction::Details
141
+
142
+ The `MVola::Transaction::Details` class provides detailed information about a transaction.
143
+
144
+ It includes the following attributes:
145
+
146
+ - `client_correlation_id`: A unique identifier for the client request.
147
+ - `raw_response`: The raw response from the MVola API, which includes:
148
+ - `amount`: The amount of the transaction.
149
+ - `currency`: The currency of the transaction.
150
+ - `request_date`: The date the transaction was requested.
151
+ - `debit_party`: Information about the debit party (e.g., phone number).
152
+ - `credit_party`: Information about the credit party (e.g., phone number).
153
+ - `fees`: Any fees associated with the transaction.
154
+ - `metadata`: Additional metadata about the transaction.
155
+ - `transaction_status`: The status of the transaction ("completed", "failed").
156
+ - `creation_date`: The date the transaction was created.
157
+ - `transaction_reference`: A reference to the transaction.
158
+
159
+ </details>
160
+
58
161
  ## Development
59
162
 
60
163
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
data/lib/mvola/client.rb CHANGED
@@ -9,7 +9,7 @@ module MVola
9
9
 
10
10
  SANDBOX_URL = "https://devapi.mvola.mg"
11
11
  PRODUCTION_URL = "https://api.mvola.mg"
12
- SANDBOX_PARTNER_PHONE_NUMBER = "0343500003"
12
+ SAFE_SANDBOX_PHONE_NUMBERS = %w[0343500003 0343500004].freeze
13
13
 
14
14
  USER_LANGUAGES = {
15
15
  fr: "FR",
@@ -38,9 +38,12 @@ module MVola
38
38
  raise ArgumentError, "consumer_secret is required" unless consumer_secret
39
39
  raise ArgumentError, "partner_name is required" unless partner_name
40
40
 
41
- partner_phone_number = SANDBOX_PARTNER_PHONE_NUMBER if sandbox
42
41
  raise ArgumentError, "partner_phone_number is required" unless partner_phone_number
43
42
 
43
+ if sandbox && !SAFE_SANDBOX_PHONE_NUMBERS.include?(partner_phone_number)
44
+ raise ArgumentError, "partner_phone_number must be one of #{SAFE_SANDBOX_PHONE_NUMBERS} in sandbox mode"
45
+ end
46
+
44
47
  # Warn invalid user language if not one of the supported languages
45
48
  unless USER_LANGUAGES.key?(user_language.downcase.to_sym)
46
49
  logger.warn "Invalid user language: #{user_language}. Using default language: #{USER_LANGUAGES[:fr]}"
data/lib/mvola/errors.rb CHANGED
@@ -3,7 +3,13 @@
3
3
  module MVola
4
4
  class Error < StandardError; end
5
5
 
6
- class InvalidRequest < Error; end
6
+ class BadRequestError < Error; end
7
7
 
8
- class Unauthorized < Error; end
8
+ class UnauthorizedError < Error; end
9
+
10
+ class NotFoundError < Error; end
11
+
12
+ class ServerError < Error; end
13
+
14
+ class ApiError < Error; end
9
15
  end
data/lib/mvola/request.rb CHANGED
@@ -70,10 +70,16 @@ module MVola
70
70
  case response.code.to_i
71
71
  when 200..299
72
72
  # Do nothing
73
+ when 400
74
+ raise MVola::BadRequestError, response.body
73
75
  when 401
74
- raise MVola::Unauthorized, response.body
76
+ raise MVola::UnauthorizedError, response.body
77
+ when 404
78
+ raise MVola::NotFoundError, response.body
79
+ when 500..599
80
+ raise MVola::ServerError, response.body
75
81
  else
76
- raise MVola::InvalidRequest, "Code: #{response.code}, Body: #{response.body}"
82
+ raise MVola::ApiError, response.body
77
83
  end
78
84
  end
79
85
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MVola
4
+ class Transaction
5
+ class Details
6
+ attr_reader :raw_response, :client_correlation_id
7
+
8
+ # @param [Hash] raw_response Hash data returned from the server
9
+ # @example of response:
10
+ # {
11
+ # "amount"=>"10000.00",
12
+ # "currency"=>"Ar",
13
+ # "requestDate"=>"2025-02-23T22:20:41.265Z",
14
+ # "debitParty"=>[{"key"=>"msisdn", "value"=>"0343500003"}],
15
+ # "creditParty"=>[{"key"=>"msisdn", "value"=>"0343500004"}],
16
+ # "fees"=>[{"feeAmount"=>"0"}],
17
+ # "metadata"=>[
18
+ # {"key"=>"originalTransactionResult", "value"=>"0"},
19
+ # {"key"=>"originalTransactionResultDesc", "value"=>"0"}
20
+ # ],
21
+ # "transactionStatus"=>"completed",
22
+ # "creationDate"=>"2025-02-23T19:19:34.707Z",
23
+ # "transactionReference"=>"653805064"
24
+ # }
25
+ # @param [String] client_correlation_id
26
+ def initialize(raw_response, client_correlation_id:)
27
+ @raw_response = raw_response
28
+ @client_correlation_id = client_correlation_id
29
+
30
+ define_delegators
31
+ end
32
+
33
+ def completed?
34
+ transaction_status == "completed"
35
+ end
36
+
37
+ def failed?
38
+ transaction_status == "failed"
39
+ end
40
+
41
+ private
42
+
43
+ # list of methods to delegate to raw_response["key"]
44
+ DELEGATED_METHOD_NAMES = %i[
45
+ amount
46
+ currency
47
+ request_date
48
+ debit_party
49
+ credit_party
50
+ fees
51
+ metadata
52
+ transaction_status
53
+ creation_date
54
+ transaction_reference
55
+ ].freeze
56
+
57
+ # Define methods to delegate to raw_response["key"]. This is done dynamically
58
+ # @example of a method:
59
+ # def transaction_status
60
+ # raw_response["transactionStatus"]
61
+ # end
62
+ def define_delegators
63
+ DELEGATED_METHOD_NAMES.each do |method_name|
64
+ self.class.send(:define_method, method_name) do
65
+ raw_response[method_name.to_s.camelcase(:lower)]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module MVola
4
4
  class Transaction
5
- # PaymentStatus is a class that represents the status of a payment.
6
- # It is used as return value of the initiate_payment! and status of a transaction.
5
+ # Class that represents the status of a transaction.
6
+ # It is used as return value of the `initiate_payment!` and the status of a transaction.
7
7
  # This is aimed to be used internally, and not exposed to the public API.
8
- class PaymentStatus
8
+ class Status
9
9
  extend Forwardable
10
10
 
11
11
  attr_reader :raw_response, :client_correlation_id
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "client"
4
- require_relative "transaction/payment_status"
4
+ require_relative "transaction/status"
5
+ require_relative "transaction/details"
5
6
 
6
7
  module MVola
7
8
  class Transaction
@@ -10,6 +11,7 @@ module MVola
10
11
  API_VERSION = "1.0.0"
11
12
  ENDPOINT = "mvola/mm/transactions/type/merchantpay/#{API_VERSION}"
12
13
  CURRENCY = "Ar"
14
+ NOT_ALLOWED_CHARACTERS_REGEXP = /[^\w\s\-._,]/
13
15
 
14
16
  attr_reader :client
15
17
 
@@ -18,44 +20,57 @@ module MVola
18
20
  end
19
21
 
20
22
  # Initiate a payment from a debit phone number to a credit phone number.
21
- # @param [Number | String] amount the amount to transfer
22
- # @param [String] debit_phone_number the phone number to debit
23
- # @param [String] credit_phone_number the phone number to credit
24
- # @param [String] transaction_reference the transaction reference
23
+ # @param [Number | String] amount the amount to transfer.
24
+ # Make sure to provide a non-decimal amount as MVola does not support decimal amounts (e.g.1000 for 1000Ar)
25
+ # @param [String] debit_phone_number the phone number to debit.
26
+ # Must be one of the safe sandbox phone numbers in sandbox mode
27
+ # @param [String] credit_phone_number the phone number to credit.
28
+ # Must be one of the safe sandbox phone numbers in sandbox mode
29
+ # @param [String] client_transaction_reference
25
30
  # @param [String] original_transaction_reference the original transaction reference.
26
- # Defaults to the `transaction_reference` if not provided
31
+ # Defaults to the `client_transaction_reference` if not provided
27
32
  # @param [String] client_correlation_id the client correlation ID. Defaults to a random UUID
28
- # @param [String] description the description of the payment
33
+ # @param [String] description the description of the payment.
34
+ # Max 50 characters long without special character except: ["-", ".", "_", ","]
29
35
  # @param [String] callback_url the callback URL to use for the payment
30
36
  # @param [Array<Hash>] metadata additional metadata to use for the payment.
31
- # Eg: [{ key: "fc", value: "USD" }, { key: "amountFc", value: "1" }]
37
+ # @example: [{ key: "fc", value: "USD" }, { key: "amountFc", value: "1" }]
32
38
  def initiate_payment!(amount:,
33
39
  debit_phone_number:,
34
40
  credit_phone_number:,
35
- transaction_reference:,
36
- original_transaction_reference: transaction_reference,
41
+ client_transaction_reference:,
42
+ description:, original_transaction_reference: client_transaction_reference,
37
43
  client_correlation_id: SecureRandom.uuid,
38
- description: nil,
39
44
  callback_url: nil,
40
45
  metadata: [])
46
+
47
+ if client.sandbox?
48
+ raise ArgumentError, "debit_phone_number must be one of #{Client::SAFE_SANDBOX_PHONE_NUMBERS} in sandbox mode" unless Client::SAFE_SANDBOX_PHONE_NUMBERS.include?(debit_phone_number)
49
+ raise ArgumentError, "credit_phone_number must be one of #{Client::SAFE_SANDBOX_PHONE_NUMBERS} in sandbox mode" unless Client::SAFE_SANDBOX_PHONE_NUMBERS.include?(credit_phone_number)
50
+ end
51
+
52
+ ensure_valid_sandbox_phone_number!(debit_phone_number, "debit_phone_number")
53
+ ensure_valid_sandbox_phone_number!(credit_phone_number, "credit_phone_number")
54
+ ensure_valid_description!(description)
55
+
41
56
  payload = {
42
57
  amount: amount.to_s,
43
58
  currency: CURRENCY,
44
- descriptionText: description,
59
+ descriptionText: description.to_s,
45
60
  requestDate: Time.now.strftime("%Y-%m-%dT%H:%M:%S.%LZ"),
46
- requestingOrganisationTransactionReference: transaction_reference,
61
+ requestingOrganisationTransactionReference: client_transaction_reference,
47
62
  originalTransactionReference: original_transaction_reference,
48
63
  debitParty: [{key: "msisdn", value: debit_phone_number}],
49
64
  creditParty: [{key: "msisdn", value: credit_phone_number}],
50
65
  metadata: [{key: "partnerName", value: client.partner_name}, *metadata]
51
66
  }
52
67
  headers = build_headers(client_correlation_id: client_correlation_id, callback_url: callback_url)
53
- logger.info "Initiating payment with payload: #{payload}"
68
+ logger.debug "Initiating payment with payload: #{payload}"
54
69
  response = post(url_for, json: payload, headers: headers)
55
- logger.info "Payment initiated. Response: #{response.body}"
70
+ logger.debug "Payment initiated. Response: #{response.body}"
56
71
 
57
72
  parsed_body = JSON.parse(response.body)
58
- PaymentStatus.new(parsed_body, client_correlation_id: client_correlation_id)
73
+ Status.new(parsed_body, client_correlation_id: client_correlation_id)
59
74
  end
60
75
 
61
76
  # Get the status of a payment using the server correlation ID.
@@ -67,7 +82,7 @@ module MVola
67
82
  response = get(url, headers: headers)
68
83
 
69
84
  parsed_body = JSON.parse(response.body)
70
- PaymentStatus.new(parsed_body, client_correlation_id: client_correlation_id)
85
+ Status.new(parsed_body, client_correlation_id: client_correlation_id)
71
86
  end
72
87
 
73
88
  # Get the details of a transaction using the transaction ID returned from the API.
@@ -75,14 +90,17 @@ module MVola
75
90
  url = url_for(transaction_id)
76
91
 
77
92
  headers = build_headers(client_correlation_id: client_correlation_id)
78
- get url, headers: headers
93
+ response = get(url, headers: headers)
94
+
95
+ parsed_body = JSON.parse(response.body)
96
+ Details.new(parsed_body, client_correlation_id: client_correlation_id)
79
97
  end
80
98
 
81
99
  private
82
100
 
83
101
  # Generate the URL for the given path by joining the base URL and the endpoint.
84
102
  def url_for(path = "")
85
- safe_path = path.gsub(/^\//, "") # Remove starting slash from the path (if any) to avoid incorrect URL generation
103
+ safe_path = path.gsub(/^\//, "") # Remove starting slash from the path (if any) to avoid incorrect URL generation
86
104
  Pathname.new(client.base_url).join(ENDPOINT, safe_path).to_s
87
105
  end
88
106
 
@@ -101,5 +119,21 @@ module MVola
101
119
 
102
120
  value
103
121
  end
122
+
123
+ def ensure_valid_sandbox_phone_number!(phone_number, param_name = "phone_number")
124
+ return unless client.sandbox?
125
+ return if phone_number.empty?
126
+ return if Client::SAFE_SANDBOX_PHONE_NUMBERS.include?(phone_number)
127
+
128
+ raise ArgumentError, "#{param_name} must be one of #{Client::SAFE_SANDBOX_PHONE_NUMBERS} in sandbox mode"
129
+ end
130
+
131
+ def ensure_valid_description!(description)
132
+ raise ArgumentError, "description is required" if description.blank?
133
+ raise ArgumentError, "description must be 50 characters long maximum" if description.size > 50
134
+
135
+ invalid_chars = description.match(NOT_ALLOWED_CHARACTERS_REGEXP)
136
+ raise ArgumentError, "description contains invalid characters: #{invalid_chars}" if invalid_chars
137
+ end
104
138
  end
105
139
  end
data/lib/mvola/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MVola
4
- VERSION = "0.1.0-alpha"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/mvola.rb CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  require "logger"
4
4
  require "base64"
5
- require "json"
6
5
  require "time"
6
+ require "active_support"
7
+ require "active_support/core_ext"
8
+ require "active_support/inflector"
7
9
  require_relative "mvola/version"
8
10
  require_relative "mvola/errors"
9
11
  require_relative "mvola/client"
@@ -16,5 +18,5 @@ module MVola
16
18
 
17
19
  # Set up a default logger
18
20
  self.logger = Logger.new($stdout) # Logs to the console
19
- logger.level = Logger::INFO # Default log level
21
+ logger.level = Logger::INFO # Default log level
20
22
  end
@@ -10,7 +10,7 @@ module MVola
10
10
 
11
11
  SANDBOX_URL: String
12
12
  PRODUCTION_URL: String
13
- SANDBOX_PARTNER_PHONE_NUMBER: String
13
+ SAFE_SANDBOX_PHONE_NUMBERS: Array[String]
14
14
  USER_LANGUAGES: Hash[Symbol, String]
15
15
 
16
16
  @consumer_key: String
@@ -0,0 +1,62 @@
1
+ module MVola
2
+ class Transaction
3
+ class Details
4
+ extend Forwardable
5
+
6
+ type t_status = 'completed' | 'failed'
7
+
8
+ type t_raw_response = {
9
+ "amount" => String,
10
+ "currency" => String,
11
+ "requestDate" => String,
12
+ "debitParty" => Array[{ "key" => "msisdn", "value" => String }],
13
+ "creditParty" => Array[{ "key" => "msisdn", "value" => String }],
14
+ "fees" => Array[{ "feeAmount" => String }],
15
+ "metadata" => Array[{ "key" => String, "value" => String }],
16
+ "transactionStatus" => t_status,
17
+ "creationDate" => String,
18
+ "transactionReference" => String
19
+ }
20
+
21
+ @raw_response: t_raw_response
22
+
23
+ @client_correlation_id: String
24
+
25
+ attr_reader raw_response: untyped
26
+
27
+ attr_reader client_correlation_id: untyped
28
+
29
+ def initialize: (t_raw_response raw_response, client_correlation_id: String) -> void
30
+
31
+ def amount: () -> String
32
+
33
+ def currency: () -> String
34
+
35
+ def request_date: () -> String
36
+
37
+ def debit_party: () -> Array[{ "key" => "msisdn", "value" => String }]
38
+
39
+ def credit_party: () -> Array[{ "key" => "msisdn", "value" => String }]
40
+
41
+ def fees: () -> Array[{ "feeAmount" => String }]
42
+
43
+ def metadata: () -> Array[{ "key" => String, "value" => String }]
44
+
45
+ def transaction_status: () -> t_status
46
+
47
+ def creation_date: () -> String
48
+
49
+ def transaction_reference: () -> String
50
+
51
+ def completed?: () -> bool
52
+
53
+ def failed?: () -> bool
54
+
55
+ private
56
+
57
+ DELEGATED_METHOD_NAMES: Array[Symbol]
58
+
59
+ def define_delegators: () -> void
60
+ end
61
+ end
62
+ end
@@ -1,6 +1,6 @@
1
1
  module MVola
2
2
  class Transaction
3
- class PaymentStatus
3
+ class Status
4
4
  extend Forwardable
5
5
 
6
6
  type t_status = 'pending' | 'completed' | 'failed'
@@ -0,0 +1,40 @@
1
+ module MVola
2
+ class Transaction
3
+ include Request
4
+
5
+
6
+ API_VERSION: String
7
+
8
+ ENDPOINT: ::String
9
+
10
+ CURRENCY: String
11
+
12
+ NOT_ALLOWED_CHARACTERS_REGEXP: Regexp
13
+
14
+ type fc_metadata = { key: "fc", value: String }
15
+ type amount_fc_metadata = { key: "amountFc", value: String }
16
+ type metadata_value = fc_metadata | amount_fc_metadata | { key: String, value: String }
17
+
18
+ @client: Client
19
+ attr_reader client: Client
20
+
21
+ def initialize: (?client: Client?) -> void
22
+
23
+ def initiate_payment!: (amount: Float | String, debit_phone_number: String, credit_phone_number: String, ?client_transaction_reference: String, ?original_transaction_reference: String, ?client_correlation_id: String, description: String, ?callback_url: String?, ?metadata: Array[metadata_value]) -> untyped
24
+
25
+ def get_status: (String server_correlation_id, ?client_correlation_id: String) -> Status
26
+
27
+ def get_details: (String transaction_id, ?client_correlation_id: String) -> Details
28
+
29
+ private
30
+
31
+ def ensure_valid_sandbox_phone_number!: (String phone_number, String parameter_name) -> void
32
+
33
+ def ensure_valid_description!: (String description) -> void
34
+
35
+ # Generate the URL for the given path by joining the base URL and the endpoint.
36
+ def url_for: (?::String path) -> String
37
+
38
+ def build_headers: (client_correlation_id: String, ?callback_url: String?) -> Hash[String, untyped]
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: MVola
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kassam
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2025-01-03 00:00:00.000000000 Z
11
- dependencies: []
11
+ date: 2025-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
12
27
  description: Library for MVola payment provider integration
13
28
  email:
14
29
  - kassam.housseny@gmail.com
@@ -16,7 +31,6 @@ executables: []
16
31
  extensions: []
17
32
  extra_rdoc_files: []
18
33
  files:
19
- - ".idea/workspace.xml"
20
34
  - ".overcommit.yml"
21
35
  - ".rspec"
22
36
  - ".standard.yml"
@@ -31,12 +45,15 @@ files:
31
45
  - lib/mvola/errors.rb
32
46
  - lib/mvola/request.rb
33
47
  - lib/mvola/transaction.rb
34
- - lib/mvola/transaction/payment_status.rb
48
+ - lib/mvola/transaction/details.rb
49
+ - lib/mvola/transaction/status.rb
35
50
  - lib/mvola/version.rb
36
51
  - sig/lib/mvola/client.rbs
37
52
  - sig/lib/mvola/client/token.rbs
38
53
  - sig/lib/mvola/request.rbs
39
- - sig/lib/mvola/transaction/payment_status.rbs
54
+ - sig/lib/mvola/transaction.rbs
55
+ - sig/lib/mvola/transaction/details.rbs
56
+ - sig/lib/mvola/transaction/status.rbs
40
57
  - sig/mvola.rbs
41
58
  homepage: https://github.com/Ksm125/mvola
42
59
  licenses:
@@ -45,6 +62,7 @@ metadata:
45
62
  homepage_uri: https://github.com/Ksm125/mvola
46
63
  source_code_uri: https://github.com/Ksm125/mvola
47
64
  changelog_uri: https://github.com/Ksm125/mvola/blob/main/CHANGELOG.md
65
+ post_install_message:
48
66
  rdoc_options: []
49
67
  require_paths:
50
68
  - lib
@@ -52,14 +70,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
52
70
  requirements:
53
71
  - - ">="
54
72
  - !ruby/object:Gem::Version
55
- version: '2.5'
73
+ version: '2.7'
56
74
  required_rubygems_version: !ruby/object:Gem::Requirement
57
75
  requirements:
58
76
  - - ">="
59
77
  - !ruby/object:Gem::Version
60
78
  version: '0'
61
79
  requirements: []
62
- rubygems_version: 3.6.2
80
+ rubygems_version: 3.5.23
81
+ signing_key:
63
82
  specification_version: 4
64
83
  summary: Library for MVola payment provider integration
65
84
  test_files: []
data/.idea/workspace.xml DELETED
@@ -1,525 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AutoImportSettings">
4
- <option name="autoReloadType" value="SELECTIVE" />
5
- </component>
6
- <component name="ChangeListManager">
7
- <list default="true" id="7ee709d9-5032-4689-a8a2-5717386c6afb" name="Changes" comment="chore: support for ruby &gt;= 2.7" />
8
- <option name="SHOW_DIALOG" value="false" />
9
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
- <option name="LAST_RESOLUTION" value="IGNORE" />
12
- </component>
13
- <component name="FileTemplateManagerImpl">
14
- <option name="RECENT_TEMPLATES">
15
- <list>
16
- <option value="Ruby Module" />
17
- <option value="Ruby Class" />
18
- <option value="RBS Class" />
19
- <option value="RSpec" />
20
- <option value="Ruby File" />
21
- </list>
22
- </option>
23
- </component>
24
- <component name="Git.Settings">
25
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
26
- </component>
27
- <component name="GitHubPullRequestSearchHistory"><![CDATA[{
28
- "lastFilter": {
29
- "state": "OPEN",
30
- "assignee": "Ksm125"
31
- }
32
- }]]></component>
33
- <component name="GithubPullRequestsUISettings"><![CDATA[{
34
- "selectedUrlAndAccountId": {
35
- "url": "https://github.com/Ksm125/mvola.git",
36
- "accountId": "af598ffa-aee9-4bd7-ac2a-98544fb8094c"
37
- }
38
- }]]></component>
39
- <component name="ProjectColorInfo">{
40
- &quot;associatedIndex&quot;: 1
41
- }</component>
42
- <component name="ProjectId" id="2qvcbbGZCFM07nWzZnSlgcZxACn" />
43
- <component name="ProjectViewState">
44
- <option name="hideEmptyMiddlePackages" value="true" />
45
- <option name="showLibraryContents" value="true" />
46
- </component>
47
- <component name="PropertiesComponent"><![CDATA[{
48
- "keyToString": {
49
- "DefaultRubyCreateTestTemplate": "RSpec",
50
- "RSpec.All specs in spec: MVola.executor": "Run",
51
- "RSpec.MVola.executor": "Run",
52
- "RSpec.MVola::Client#token fetches a new token from provider if no token defined.executor": "Debug",
53
- "RSpec.MVola::Client#token only fetches token once if called multiple times.executor": "Run",
54
- "RSpec.MVola::Client#token refreshes the token if expires margin is reached.executor": "Run",
55
- "RSpec.MVola::Client#token refreshes the token if no longer valid.executor": "Run",
56
- "RSpec.MVola::Client#token when sandbox is false.executor": "Run",
57
- "RSpec.MVola::Client#token when token is provided uses the provided token.executor": "Run",
58
- "RSpec.MVola::Client#token when token is provided when token is a Token object.executor": "Run",
59
- "RSpec.MVola::Client#token when token is provided when token is a hash uses the provided token.executor": "Run",
60
- "RSpec.MVola::Client#token when token is provided when token is a hash.executor": "Run",
61
- "RSpec.MVola::Client#token! forces the token to be refreshed.executor": "Run",
62
- "RSpec.MVola::Client.executor": "Run",
63
- "RunOnceActivity.ShowReadmeOnStart": "true",
64
- "RunOnceActivity.git.unshallow": "true",
65
- "git-widget-placeholder": "main",
66
- "last_opened_file_path": "/Users/kassamhousseny/project/MVola",
67
- "node.js.detected.package.eslint": "true",
68
- "node.js.detected.package.tslint": "true",
69
- "node.js.selected.package.eslint": "(autodetect)",
70
- "node.js.selected.package.tslint": "(autodetect)",
71
- "nodejs_package_manager_path": "npm",
72
- "ruby.structure.view.model.defaults.configured": "true",
73
- "settings.editor.selected.configurable": "preferences.editor.code.editing",
74
- "vue.rearranger.settings.migration": "true"
75
- }
76
- }]]></component>
77
- <component name="RecentsManager">
78
- <key name="CopyFile.RECENT_KEYS">
79
- <recent name="$PROJECT_DIR$" />
80
- </key>
81
- </component>
82
- <component name="RubocopSettings">
83
- <option name="useStandard" value="true" />
84
- </component>
85
- <component name="RunManager" selected="RSpec.All specs in spec: MVola">
86
- <configuration name="All specs in spec: MVola" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true">
87
- <module name="MVola" />
88
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
89
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
90
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
91
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
92
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
93
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
94
- <envs>
95
- <env name="JRUBY_OPTS" value="-X+O" />
96
- </envs>
97
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
98
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
99
- <COVERAGE_PATTERN ENABLED="true">
100
- <PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
101
- </COVERAGE_PATTERN>
102
- </EXTENSION>
103
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
104
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="$MODULE_DIR$/spec" />
105
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="" />
106
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
107
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
108
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
109
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="" />
110
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
111
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
112
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="ALL_IN_FOLDER" />
113
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
114
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
115
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
116
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
117
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
118
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
119
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
120
- <method v="2" />
121
- </configuration>
122
- <configuration name="MVola::Client" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
123
- <module name="MVola" />
124
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
125
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
126
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
127
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
128
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
129
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
130
- <envs>
131
- <env name="JRUBY_OPTS" value="-X+O" />
132
- </envs>
133
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
134
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
135
- <COVERAGE_PATTERN ENABLED="true">
136
- <PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
137
- </COVERAGE_PATTERN>
138
- </EXTENSION>
139
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
140
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
141
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/mvola/client_spec.rb" />
142
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
143
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
144
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
145
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="MVola::Client" />
146
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
147
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
148
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
149
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
150
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
151
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
152
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
153
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
154
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
155
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
156
- <method v="2" />
157
- </configuration>
158
- <configuration name="MVola::Client#token when token is provided when token is a Token object" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
159
- <module name="MVola" />
160
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
161
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
162
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
163
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
164
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
165
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
166
- <envs>
167
- <env name="JRUBY_OPTS" value="-X+O" />
168
- </envs>
169
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
170
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
171
- <COVERAGE_PATTERN ENABLED="true">
172
- <PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
173
- </COVERAGE_PATTERN>
174
- </EXTENSION>
175
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
176
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
177
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/mvola/client_spec.rb" />
178
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
179
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
180
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
181
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="MVola::Client#token when token is provided when token is a Token object" />
182
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
183
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
184
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
185
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
186
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
187
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
188
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
189
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
190
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
191
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
192
- <method v="2" />
193
- </configuration>
194
- <configuration name="MVola::Client#token when token is provided when token is a hash uses the provided token" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
195
- <module name="MVola" />
196
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
197
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
198
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
199
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
200
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
201
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
202
- <envs>
203
- <env name="JRUBY_OPTS" value="-X+O" />
204
- </envs>
205
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
206
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
207
- <COVERAGE_PATTERN ENABLED="true">
208
- <PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
209
- </COVERAGE_PATTERN>
210
- </EXTENSION>
211
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
212
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
213
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/mvola/client_spec.rb" />
214
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
215
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
216
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
217
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="MVola::Client#token when token is provided when token is a hash uses the provided token" />
218
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
219
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
220
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
221
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
222
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
223
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
224
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
225
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
226
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
227
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
228
- <method v="2" />
229
- </configuration>
230
- <configuration name="MVola::Client#token when token is provided when token is a hash" type="RSpecRunConfigurationType" factoryName="RSpec" temporary="true" nameIsGenerated="true">
231
- <module name="MVola" />
232
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
233
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
234
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="$MODULE_DIR$" />
235
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
236
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
237
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
238
- <envs>
239
- <env name="JRUBY_OPTS" value="-X+O" />
240
- </envs>
241
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
242
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" track_test_folders="true" runner="rcov" ENABLE_BRANCH_COVERAGE="true" ENABLE_FORKED_COVERAGE="true">
243
- <COVERAGE_PATTERN ENABLED="true">
244
- <PATTERN REGEXPS="/.rvm/" INCLUDED="false" />
245
- </COVERAGE_PATTERN>
246
- </EXTENSION>
247
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
248
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="" />
249
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="$MODULE_DIR$/spec/mvola/client_spec.rb" />
250
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
251
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
252
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
253
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="MVola::Client#token when token is provided when token is a hash" />
254
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
255
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
256
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="TEST_SCRIPT" />
257
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
258
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
259
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
260
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
261
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
262
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
263
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
264
- <method v="2" />
265
- </configuration>
266
- <recent_temporary>
267
- <list>
268
- <item itemvalue="RSpec.All specs in spec: MVola" />
269
- <item itemvalue="RSpec.MVola::Client" />
270
- <item itemvalue="RSpec.MVola::Client#token when token is provided when token is a Token object" />
271
- <item itemvalue="RSpec.MVola::Client#token when token is provided when token is a hash uses the provided token" />
272
- <item itemvalue="RSpec.MVola::Client#token when token is provided when token is a hash" />
273
- </list>
274
- </recent_temporary>
275
- </component>
276
- <component name="SharedIndexes">
277
- <attachedChunks>
278
- <set>
279
- <option value="bundled-js-predefined-d6986cc7102b-deb605915726-JavaScript-RM-243.22562.213" />
280
- </set>
281
- </attachedChunks>
282
- </component>
283
- <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
284
- <component name="SpringUtil" SPRING_PRE_LOADER_OPTION="true" RAKE_SPRING_PRE_LOADER_OPTION="true" RAILS_SPRING_PRE_LOADER_OPTION="true" />
285
- <component name="TaskManager">
286
- <task active="true" id="Default" summary="Default task">
287
- <changelist id="7ee709d9-5032-4689-a8a2-5717386c6afb" name="Changes" comment="" />
288
- <created>1735546561998</created>
289
- <option name="number" value="Default" />
290
- <option name="presentableId" value="Default" />
291
- <updated>1735546561998</updated>
292
- <workItem from="1735546563290" duration="476000" />
293
- <workItem from="1735547047278" duration="445000" />
294
- <workItem from="1735547500779" duration="22590000" />
295
- </task>
296
- <task id="LOCAL-00001" summary="First commit">
297
- <option name="closed" value="true" />
298
- <created>1735546602240</created>
299
- <option name="number" value="00001" />
300
- <option name="presentableId" value="LOCAL-00001" />
301
- <option name="project" value="LOCAL" />
302
- <updated>1735546602240</updated>
303
- </task>
304
- <task id="LOCAL-00002" summary="bundle install">
305
- <option name="closed" value="true" />
306
- <created>1735547117884</created>
307
- <option name="number" value="00002" />
308
- <option name="presentableId" value="LOCAL-00002" />
309
- <option name="project" value="LOCAL" />
310
- <updated>1735547117884</updated>
311
- </task>
312
- <task id="LOCAL-00003" summary="Add Mvola client">
313
- <option name="closed" value="true" />
314
- <created>1735561464502</created>
315
- <option name="number" value="00003" />
316
- <option name="presentableId" value="LOCAL-00003" />
317
- <option name="project" value="LOCAL" />
318
- <updated>1735561464502</updated>
319
- </task>
320
- <task id="LOCAL-00004" summary="chore: Add forwadable to request">
321
- <option name="closed" value="true" />
322
- <created>1735563630500</created>
323
- <option name="number" value="00004" />
324
- <option name="presentableId" value="LOCAL-00004" />
325
- <option name="project" value="LOCAL" />
326
- <updated>1735563630500</updated>
327
- </task>
328
- <task id="LOCAL-00005" summary="Add test">
329
- <option name="closed" value="true" />
330
- <created>1735567824712</created>
331
- <option name="number" value="00005" />
332
- <option name="presentableId" value="LOCAL-00005" />
333
- <option name="project" value="LOCAL" />
334
- <updated>1735567824712</updated>
335
- </task>
336
- <task id="LOCAL-00006" summary="Add support of reuse provided token to the client">
337
- <option name="closed" value="true" />
338
- <created>1735570505239</created>
339
- <option name="number" value="00006" />
340
- <option name="presentableId" value="LOCAL-00006" />
341
- <option name="project" value="LOCAL" />
342
- <updated>1735570505239</updated>
343
- </task>
344
- <task id="LOCAL-00007" summary="chore: update ci">
345
- <option name="closed" value="true" />
346
- <created>1735570907110</created>
347
- <option name="number" value="00007" />
348
- <option name="presentableId" value="LOCAL-00007" />
349
- <option name="project" value="LOCAL" />
350
- <updated>1735570907110</updated>
351
- </task>
352
- <task id="LOCAL-00008" summary="chore: update gemspec">
353
- <option name="closed" value="true" />
354
- <created>1735571014085</created>
355
- <option name="number" value="00008" />
356
- <option name="presentableId" value="LOCAL-00008" />
357
- <option name="project" value="LOCAL" />
358
- <updated>1735571014085</updated>
359
- </task>
360
- <task id="LOCAL-00009" summary="chore: update bundler version">
361
- <option name="closed" value="true" />
362
- <created>1735571341431</created>
363
- <option name="number" value="00009" />
364
- <option name="presentableId" value="LOCAL-00009" />
365
- <option name="project" value="LOCAL" />
366
- <updated>1735571341431</updated>
367
- </task>
368
- <task id="LOCAL-00010" summary="chore: support for ruby 2.5">
369
- <option name="closed" value="true" />
370
- <created>1735571747042</created>
371
- <option name="number" value="00010" />
372
- <option name="presentableId" value="LOCAL-00010" />
373
- <option name="project" value="LOCAL" />
374
- <updated>1735571747042</updated>
375
- </task>
376
- <task id="LOCAL-00011" summary="chore: support for ruby 2.5">
377
- <option name="closed" value="true" />
378
- <created>1735571921726</created>
379
- <option name="number" value="00011" />
380
- <option name="presentableId" value="LOCAL-00011" />
381
- <option name="project" value="LOCAL" />
382
- <updated>1735571921726</updated>
383
- </task>
384
- <task id="LOCAL-00012" summary="chore: support for ruby 2">
385
- <option name="closed" value="true" />
386
- <created>1735572167773</created>
387
- <option name="number" value="00012" />
388
- <option name="presentableId" value="LOCAL-00012" />
389
- <option name="project" value="LOCAL" />
390
- <updated>1735572167773</updated>
391
- </task>
392
- <task id="LOCAL-00013" summary="chore: support for ruby 2">
393
- <option name="closed" value="true" />
394
- <created>1735572398606</created>
395
- <option name="number" value="00013" />
396
- <option name="presentableId" value="LOCAL-00013" />
397
- <option name="project" value="LOCAL" />
398
- <updated>1735572398606</updated>
399
- </task>
400
- <task id="LOCAL-00014" summary="chore: support for ruby 2">
401
- <option name="closed" value="true" />
402
- <created>1735572428782</created>
403
- <option name="number" value="00014" />
404
- <option name="presentableId" value="LOCAL-00014" />
405
- <option name="project" value="LOCAL" />
406
- <updated>1735572428782</updated>
407
- </task>
408
- <task id="LOCAL-00015" summary="chore: support for ruby 2">
409
- <option name="closed" value="true" />
410
- <created>1735572643584</created>
411
- <option name="number" value="00015" />
412
- <option name="presentableId" value="LOCAL-00015" />
413
- <option name="project" value="LOCAL" />
414
- <updated>1735572643584</updated>
415
- </task>
416
- <task id="LOCAL-00016" summary="chore: support for ruby 2">
417
- <option name="closed" value="true" />
418
- <created>1735572747247</created>
419
- <option name="number" value="00016" />
420
- <option name="presentableId" value="LOCAL-00016" />
421
- <option name="project" value="LOCAL" />
422
- <updated>1735572747247</updated>
423
- </task>
424
- <task id="LOCAL-00017" summary="chore: support for ruby 2">
425
- <option name="closed" value="true" />
426
- <created>1735572918195</created>
427
- <option name="number" value="00017" />
428
- <option name="presentableId" value="LOCAL-00017" />
429
- <option name="project" value="LOCAL" />
430
- <updated>1735572918195</updated>
431
- </task>
432
- <task id="LOCAL-00018" summary="chore: support for ruby 2">
433
- <option name="closed" value="true" />
434
- <created>1735573047052</created>
435
- <option name="number" value="00018" />
436
- <option name="presentableId" value="LOCAL-00018" />
437
- <option name="project" value="LOCAL" />
438
- <updated>1735573047053</updated>
439
- </task>
440
- <task id="LOCAL-00019" summary="chore: support for ruby 2">
441
- <option name="closed" value="true" />
442
- <created>1735573294107</created>
443
- <option name="number" value="00019" />
444
- <option name="presentableId" value="LOCAL-00019" />
445
- <option name="project" value="LOCAL" />
446
- <updated>1735573294107</updated>
447
- </task>
448
- <task id="LOCAL-00020" summary="chore: support for ruby &gt;= 2.7">
449
- <option name="closed" value="true" />
450
- <created>1735573377240</created>
451
- <option name="number" value="00020" />
452
- <option name="presentableId" value="LOCAL-00020" />
453
- <option name="project" value="LOCAL" />
454
- <updated>1735573377240</updated>
455
- </task>
456
- <option name="localTasksCounter" value="21" />
457
- <servers />
458
- </component>
459
- <component name="TypeScriptGeneratedFilesManager">
460
- <option name="version" value="3" />
461
- </component>
462
- <component name="Vcs.Log.Tabs.Properties">
463
- <option name="OPEN_GENERIC_TABS">
464
- <map>
465
- <entry key="11bdcca4-6bc3-47a8-9892-523b7ae40d87" value="TOOL_WINDOW" />
466
- </map>
467
- </option>
468
- <option name="TAB_STATES">
469
- <map>
470
- <entry key="11bdcca4-6bc3-47a8-9892-523b7ae40d87">
471
- <value>
472
- <State />
473
- </value>
474
- </entry>
475
- <entry key="MAIN">
476
- <value>
477
- <State>
478
- <option name="FILTERS">
479
- <map>
480
- <entry key="branch">
481
- <value>
482
- <list>
483
- <option value="main" />
484
- </list>
485
- </value>
486
- </entry>
487
- </map>
488
- </option>
489
- </State>
490
- </value>
491
- </entry>
492
- </map>
493
- </option>
494
- </component>
495
- <component name="VcsManagerConfiguration">
496
- <MESSAGE value="First commit" />
497
- <MESSAGE value="bundle install" />
498
- <MESSAGE value="Add Mvola client" />
499
- <MESSAGE value="chore: Add forwadable to request" />
500
- <MESSAGE value="Add test" />
501
- <MESSAGE value="Add support of reuse provided token to the client" />
502
- <MESSAGE value="chore: update ci" />
503
- <MESSAGE value="chore: update gemspec" />
504
- <MESSAGE value="chore: update bundler version" />
505
- <MESSAGE value="chore: support for ruby 2.5" />
506
- <MESSAGE value="chore: support for ruby 2" />
507
- <MESSAGE value="chore: support for ruby &gt;= 2.7" />
508
- <option name="LAST_COMMIT_MESSAGE" value="chore: support for ruby &gt;= 2.7" />
509
- </component>
510
- <component name="com.intellij.coverage.CoverageDataManagerImpl">
511
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_refreshes_the_token_if_expires_margin_is_reached.rcov" NAME="MVola::Client#token refreshes the token if expires margin is reached Coverage Results" MODIFIED="1735567486982" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
512
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_when_token_is_provided_when_token_is_a_Token_object.rcov" NAME="MVola::Client#token when token is provided when token is a Token object Coverage Results" MODIFIED="1735570284693" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
513
- <SUITE FILE_PATH="coverage/MVola@MVola__Client.rcov" NAME="MVola::Client Coverage Results" MODIFIED="1735570342652" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
514
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_refreshes_the_token_if_no_longer_valid.rcov" NAME="MVola::Client#token refreshes the token if no longer valid Coverage Results" MODIFIED="1735567436479" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
515
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_when_sandbox_is_false.rcov" NAME="MVola::Client#token when sandbox is false Coverage Results" MODIFIED="1735567647666" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
516
- <SUITE FILE_PATH="coverage/MVola@All_specs_in_spec__MVola.rcov" NAME="All specs in spec: MVola Coverage Results" MODIFIED="1735572137941" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
517
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_fetches_a_new_token_from_provider_if_no_token_defined.rcov" NAME="MVola::Client#token fetches a new token from provider if no token defined Coverage Results" MODIFIED="1735570585651" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
518
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_only_fetches_token_once_if_called_multiple_times.rcov" NAME="MVola::Client#token only fetches token once if called multiple times Coverage Results" MODIFIED="1735567047662" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
519
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token__forces_the_token_to_be_refreshed.rcov" NAME="MVola::Client#token! forces the token to be refreshed Coverage Results" MODIFIED="1735567730827" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
520
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_when_token_is_provided_when_token_is_a_hash.rcov" NAME="MVola::Client#token when token is provided when token is a hash Coverage Results" MODIFIED="1735570191807" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
521
- <SUITE FILE_PATH="coverage/MVola@MVola.rcov" NAME="MVola Coverage Results" MODIFIED="1735561351336" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
522
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_when_token_is_provided_uses_the_provided_token.rcov" NAME="MVola::Client#token when token is provided uses the provided token Coverage Results" MODIFIED="1735569998034" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
523
- <SUITE FILE_PATH="coverage/MVola@MVola__Client_token_when_token_is_provided_when_token_is_a_hash_uses_the_provided_token.rcov" NAME="MVola::Client#token when token is provided when token is a hash uses the provided token Coverage Results" MODIFIED="1735570225099" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="MVola" />
524
- </component>
525
- </project>