orange_payment_api 0.1.1

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: dd8a4748b03dfefb0af34aa951483bc6c8e7dfe202cb34a5b18bd2f5bcf6eafd
4
+ data.tar.gz: ddd46e460e2a2369155fb010645fdd3efd38d1c23251169a8680e15019b02cb2
5
+ SHA512:
6
+ metadata.gz: f21e35ed9cc28376796c4fb7974d70df061ab6e97bc3102abe9495544cf21c52762ed88604c6caf3aac27eda2a98ab1fba1071f4df64b3889c7b132c54c31b19
7
+ data.tar.gz: b03d7ed4be8d24a98491ac08739744541a32f9b3aaebfedc3f86c534d018acfa29d91dadc298db7fdb4450c7e148b976ee0c8b57ddb8826424ef1b99115bb0af
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
data/.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/orange_payment_api.iml" filepath="$PROJECT_DIR$/.idea/orange_payment_api.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="ModuleRunConfigurationManager">
4
+ <shared />
5
+ </component>
6
+ <component name="NewModuleRootManager">
7
+ <content url="file://$MODULE_DIR$" />
8
+ <orderEntry type="jdk" jdkName="rbenv: 3.2.2" jdkType="RUBY_SDK" />
9
+ <orderEntry type="sourceFolder" forTests="false" />
10
+ <orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.8, rbenv: 3.2.2) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.3, rbenv: 3.2.2) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="base64 (v0.3.0, rbenv: 3.2.2) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="bigdecimal (v4.0.1, rbenv: 3.2.2) [gem]" level="application" />
14
+ <orderEntry type="library" scope="PROVIDED" name="bump (v0.10.0, rbenv: 3.2.2) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.4.22, rbenv: 3.2.2) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="childprocess (v5.1.0, rbenv: 3.2.2) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.6, rbenv: 3.2.2) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="crack (v1.0.1, rbenv: 3.2.2) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.6.2, rbenv: 3.2.2) [gem]" level="application" />
20
+ <orderEntry type="library" scope="TEST" name="faker (v2.22.0, rbenv: 3.2.2) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="hashdiff (v1.2.1, rbenv: 3.2.2) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="i18n (v1.14.8, rbenv: 3.2.2) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="iniparse (v1.5.0, rbenv: 3.2.2) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="json (v2.18.0, rbenv: 3.2.2) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.5, rbenv: 3.2.2) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="lint_roller (v1.1.0, rbenv: 3.2.2) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="logger (v1.7.0, rbenv: 3.2.2) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="overcommit (v0.68.0, rbenv: 3.2.2) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="parallel (v1.27.0, rbenv: 3.2.2) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="parser (v3.3.10.1, rbenv: 3.2.2) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="prism (v1.8.0, rbenv: 3.2.2) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="public_suffix (v4.0.7, rbenv: 3.2.2) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="racc (v1.8.1, rbenv: 3.2.2) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, rbenv: 3.2.2) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="rake (v13.3.1, rbenv: 3.2.2) [gem]" level="application" />
36
+ <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.11.3, rbenv: 3.2.2) [gem]" level="application" />
37
+ <orderEntry type="library" scope="PROVIDED" name="rexml (v3.4.4, rbenv: 3.2.2) [gem]" level="application" />
38
+ <orderEntry type="library" scope="TEST" name="rspec (v3.13.2, rbenv: 3.2.2) [gem]" level="application" />
39
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.13.6, rbenv: 3.2.2) [gem]" level="application" />
40
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.13.5, rbenv: 3.2.2) [gem]" level="application" />
41
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.13.7, rbenv: 3.2.2) [gem]" level="application" />
42
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.13.6, rbenv: 3.2.2) [gem]" level="application" />
43
+ <orderEntry type="library" scope="PROVIDED" name="rubocop (v1.64.1, rbenv: 3.2.2) [gem]" level="application" />
44
+ <orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.49.0, rbenv: 3.2.2) [gem]" level="application" />
45
+ <orderEntry type="library" scope="PROVIDED" name="rubocop-performance (v1.21.1, rbenv: 3.2.2) [gem]" level="application" />
46
+ <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, rbenv: 3.2.2) [gem]" level="application" />
47
+ <orderEntry type="library" scope="PROVIDED" name="securerandom (v0.3.2, rbenv: 3.2.2) [gem]" level="application" />
48
+ <orderEntry type="library" scope="TEST" name="standard (v1.37.0, rbenv: 3.2.2) [gem]" level="application" />
49
+ <orderEntry type="library" scope="PROVIDED" name="standard-custom (v1.0.2, rbenv: 3.2.2) [gem]" level="application" />
50
+ <orderEntry type="library" scope="TEST" name="standard-performance (v1.4.0, rbenv: 3.2.2) [gem]" level="application" />
51
+ <orderEntry type="library" scope="TEST" name="timecop (v0.9.10, rbenv: 3.2.2) [gem]" level="application" />
52
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.6.0, rbenv: 3.2.2) [gem]" level="application" />
53
+ <orderEntry type="library" scope="TEST" name="webmock (v3.26.1, rbenv: 3.2.2) [gem]" level="application" />
54
+ </component>
55
+ </module>
data/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,121 @@
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="00235933-b7e5-4761-b108-46473ad20c31" name="Changes" comment="feat: add sandbox option to Client and update token structure">
8
+ <change beforePath="$PROJECT_DIR$/.github/workflows/standard.yml" beforeDir="false" afterPath="$PROJECT_DIR$/.github/workflows/standard.yml" afterDir="false" />
9
+ </list>
10
+ <option name="SHOW_DIALOG" value="false" />
11
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
12
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
13
+ <option name="LAST_RESOLUTION" value="IGNORE" />
14
+ </component>
15
+ <component name="CopilotPersistence">
16
+ <persistenceIdMap>
17
+ <entry key="_/Users/kassamhousseny/project/orange_payment_api" value="36ebkT3z9iPaZTh49YS5Dv6GdXq" />
18
+ </persistenceIdMap>
19
+ </component>
20
+ <component name="CopilotUserSelectedChatMode">
21
+ <option name="chatModeId" value="Agent" />
22
+ </component>
23
+ <component name="CopilotUserSelectedModel">
24
+ <selectedModels>
25
+ <entry key="chat-panel" value="Gemini 3.1 Pro" />
26
+ <entry key="agent-panel" value="Gemini 3.1 Pro" />
27
+ </selectedModels>
28
+ </component>
29
+ <component name="EmbeddingIndexingInfo">
30
+ <option name="cachedIndexableFilesCount" value="36" />
31
+ <option name="fileBasedEmbeddingIndicesEnabled" value="true" />
32
+ </component>
33
+ <component name="Git.Settings">
34
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
35
+ </component>
36
+ <component name="McpProjectServerCommands">
37
+ <commands />
38
+ <urls />
39
+ </component>
40
+ <component name="ProjectColorInfo">{
41
+ &quot;customColor&quot;: &quot;&quot;,
42
+ &quot;associatedIndex&quot;: 5
43
+ }</component>
44
+ <component name="ProjectId" id="36ebkT3z9iPaZTh49YS5Dv6GdXq" />
45
+ <component name="ProjectViewState">
46
+ <option name="hideEmptyMiddlePackages" value="true" />
47
+ <option name="showLibraryContents" value="true" />
48
+ </component>
49
+ <component name="PropertiesComponent">{
50
+ &quot;keyToString&quot;: {
51
+ &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
52
+ &quot;RunOnceActivity.MCP Project settings loaded&quot;: &quot;true&quot;,
53
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
54
+ &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
55
+ &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
56
+ &quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
57
+ &quot;com.intellij.lang.ruby.rbs.tools.collection.workspace.sync.RbsCollectionUpdateProjectActivity#LAST_UPDATE_TIMESTAMP&quot;: &quot;1769352231313&quot;,
58
+ &quot;com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1&quot;: &quot;true&quot;,
59
+ &quot;git-widget-placeholder&quot;: &quot;main&quot;,
60
+ &quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
61
+ &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
62
+ &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
63
+ &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
64
+ &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
65
+ &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
66
+ &quot;ruby.structure.view.model.defaults.configured&quot;: &quot;true&quot;,
67
+ &quot;settings.editor.selected.configurable&quot;: &quot;ruby.rubocop&quot;,
68
+ &quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
69
+ &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
70
+ }
71
+ }</component>
72
+ <component name="RubocopSettings">
73
+ <option name="configFilePath" value="/.standard.yml" />
74
+ <option name="useStandard" value="true" />
75
+ </component>
76
+ <component name="SharedIndexes">
77
+ <attachedChunks>
78
+ <set>
79
+ <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-RM-253.31033.144" />
80
+ </set>
81
+ </attachedChunks>
82
+ </component>
83
+ <component name="SpringUtil" SPRING_PRE_LOADER_OPTION="true" RAKE_SPRING_PRE_LOADER_OPTION="true" RAILS_SPRING_PRE_LOADER_OPTION="true" />
84
+ <component name="TaskManager">
85
+ <task active="true" id="Default" summary="Default task">
86
+ <changelist id="00235933-b7e5-4761-b108-46473ad20c31" name="Changes" comment="" />
87
+ <created>1765370127421</created>
88
+ <option name="number" value="Default" />
89
+ <option name="presentableId" value="Default" />
90
+ <updated>1765370127421</updated>
91
+ <workItem from="1765370130261" duration="12000" />
92
+ <workItem from="1769352172948" duration="453000" />
93
+ <workItem from="1775731401588" duration="16000" />
94
+ </task>
95
+ <task id="LOCAL-00001" summary="feat: add sandbox option to Client and update token structure">
96
+ <option name="closed" value="true" />
97
+ <created>1775731940800</created>
98
+ <option name="number" value="00001" />
99
+ <option name="presentableId" value="LOCAL-00001" />
100
+ <option name="project" value="LOCAL" />
101
+ <updated>1775731940800</updated>
102
+ </task>
103
+ <option name="localTasksCounter" value="2" />
104
+ <servers />
105
+ </component>
106
+ <component name="VcsManagerConfiguration">
107
+ <MESSAGE value="feat: add sandbox option to Client and update token structure" />
108
+ <option name="LAST_COMMIT_MESSAGE" value="feat: add sandbox option to Client and update token structure" />
109
+ </component>
110
+ <component name="com.intellij.coverage.CoverageDataManagerImpl">
111
+ <SUITE FILE_PATH="coverage/orange_payment_api@All_specs_in_spec__orange_payment_api.rcov" NAME="All specs in spec: orange_payment_api Coverage Results" MODIFIED="1775731870219" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" MODULE_NAME="orange_payment_api" />
112
+ </component>
113
+ <component name="github-copilot-workspace">
114
+ <instructionFileLocations>
115
+ <option value=".github/instructions" />
116
+ </instructionFileLocations>
117
+ <promptFileLocations>
118
+ <option value=".github/prompts" />
119
+ </promptFileLocations>
120
+ </component>
121
+ </project>
data/.overcommit.yml ADDED
@@ -0,0 +1,20 @@
1
+ PreCommit:
2
+ Standard:
3
+ enabled: true
4
+ on_warn: warn
5
+ problem_on_unmodified_line: ignore
6
+ command: ['bundle', 'exec', 'standardrb']
7
+ exclude:
8
+ - 'bin/**/*'
9
+
10
+
11
+ TrailingWhitespace:
12
+ enabled: true
13
+ exclude:
14
+ - '**/db/structure.sql' # Ignore trailing whitespace in generated files
15
+
16
+ PrePush:
17
+ RSpec:
18
+ enabled: true
19
+ required: true
20
+ command: [ 'bundle', 'exec', 'rspec' ]
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## [Unreleased]
2
+ ## [0.1.1] - 2026-04-09
3
+
4
+ ## [0.1.0] - 2025-12-10
5
+
6
+ - Initial release
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "orange_payment_api" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["kassam.housseny@gmail.com"](mailto:"kassam.housseny@gmail.com").
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Kassam
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,227 @@
1
+ # OrangePaymentApi
2
+
3
+ <img width="372" height="135" alt="orange_money" src="https://github.com/user-attachments/assets/db4aeda6-ffe8-46c2-aac6-a73e995ec916" />
4
+
5
+ OrangePaymentApi is a Ruby gem that provides a simple way to interact with the [Orange Money Payment API](https://developer.orange.com/apis/orange-money-webpay/).
6
+
7
+ ## Installation
8
+
9
+ ### Install from RubyGems (once published)
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ ```bash
14
+ bundle add orange_payment_api
15
+ ```
16
+
17
+ If bundler is not being used to manage dependencies, install the gem by executing:
18
+
19
+ ```bash
20
+ gem install orange_payment_api
21
+ ```
22
+
23
+ ### Install from local repository (for testing before publishing)
24
+
25
+ If you want to test this gem in another project without publishing it first, you have several options:
26
+
27
+ #### Option 1: Using a local path in your Gemfile
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'orange_payment_api', path: '/path/to/orange_payment_api'
33
+ ```
34
+
35
+ Then execute:
36
+
37
+ ```bash
38
+ bundle install
39
+ ```
40
+
41
+ #### Option 2: Using a Git repository
42
+
43
+ If the gem is in a Git repository, add this to your Gemfile:
44
+
45
+ ```ruby
46
+ gem 'orange_payment_api', git: 'https://github.com/AnTsena/orange_payment_api.git'
47
+ # Or use a specific branch:
48
+ # gem 'orange_payment_api', git: 'https://github.com/AnTsena/orange_payment_api.git', branch: 'main'
49
+ ```
50
+
51
+ Then execute:
52
+
53
+ ```bash
54
+ bundle install
55
+ ```
56
+
57
+ #### Option 3: Build and install locally
58
+
59
+ From the orange_payment_api directory, build and install the gem locally:
60
+
61
+ ```bash
62
+ # Build the gem
63
+ gem build orange_payment_api.gemspec
64
+
65
+ # Install the built gem
66
+ gem install ./orange_payment_api-0.1.0.gem
67
+ ```
68
+
69
+ Then in your application's Gemfile:
70
+
71
+ ```ruby
72
+ gem 'orange_payment_api', '~> 0.1.0'
73
+ ```
74
+
75
+ ## Usage
76
+
77
+ Before using the gem, ensure you have the credentials required to interact with the Orange Money API (**Client ID**, **Client Secret**, and **Merchant Key**).
78
+
79
+ ### Get Access Token
80
+
81
+ To get an access token, use the `OrangePaymentApi::Client` class and call the `token` method:
82
+
83
+ ```ruby
84
+ client = OrangePaymentApi::Client.new(client_id: 'your_client_id', client_secret: 'your_client_secret')
85
+
86
+ client.token # <OrangePaymentApi::Client::Token access_token="your_access_token", token_type="Bearer", expires_at=2024-12-30 16:19:42 +0100>
87
+ ```
88
+
89
+ The `token` method stores the data at instance level, so when called multiple times, we don't have to request a new token each time, until the token expires.
90
+ If you want to force a new token, you can use the method:
91
+ ```ruby
92
+ client.token!
93
+ ```
94
+
95
+ #### Tips
96
+
97
+ We suggest that you store the token in a cache system (like `Redis`, `Memcached`) and pass the value directly
98
+ into the client to avoid requesting a new token each time, across multiple instances of the application:
99
+
100
+ ```ruby
101
+ token = client.token
102
+ # Store the value in cache
103
+ cache.write('orange_token', token.to_h)
104
+
105
+ # then next time, you can use the value from cache
106
+ token = cache.read('orange_token')
107
+
108
+ client = OrangePaymentApi::Client.new(client_id: 'your_client_id', client_secret: 'your_client_secret', token: token)
109
+
110
+ # If the token is still valid, the client will use it directly without requesting a new one.
111
+ # If it's expired, the client will automatically request a new one to make a request.
112
+ client.token
113
+ ```
114
+
115
+ ### Initialize a Payment
116
+
117
+ To initialize a payment, use the `OrangePaymentApi::Transaction` class and call the `initiate_payment!` method:
118
+
119
+ ```ruby
120
+ ENV["ORANGE_PAYMENT_API_CLIENT_ID"] = "CLIENT_ID"
121
+ ENV["ORANGE_PAYMENT_API_CLIENT_SECRET"] = "CLIENT_SECRET"
122
+
123
+ merchant_key = "your_merchant_key"
124
+ client = OrangePaymentApi::Client.new(sandbox: true)
125
+
126
+ transaction = OrangePaymentApi::Transaction.new(client: client, sandbox: true)
127
+ # OR (if you want to use the default client)
128
+ # transaction = OrangePaymentApi::Transaction.new(sandbox: true)
129
+
130
+ order_id = SecureRandom.hex(10)
131
+
132
+ # Initiate a payment
133
+ response = transaction.initiate_payment!(
134
+ amount: 1000.0,
135
+ merchant_key: merchant_key,
136
+ order_id: order_id,
137
+ currency: "MGA",
138
+ return_url: "http://www.merchant-example.org/return",
139
+ cancel_url: "http://www.merchant-example.org/cancel",
140
+ notif_url: "http://www.merchant-example.org/notif"
141
+ )
142
+
143
+ pp response
144
+ # => #<struct OrangePaymentApi::Transaction::CreatedInfo
145
+ # status=201,
146
+ # message="OK",
147
+ # pay_token="v1pszsvkuxjmdzodwafycf833nxccmt8dviiuoyya79md4fzm4sryo10q6v0pdx7",
148
+ # payment_url="https://mpayment.orange-money.com/sx/mpayment/abstract/v1pszsvkuxjmdzodwafycf833nxccmt8dviiuoyya79md4fzm4sryo10q6v0pdx7",
149
+ # notif_token="0pxpzs95hi1ndzsxcbfjycq7i70dfcyz">
150
+
151
+ # Redirect the user to response.payment_url to complete the payment
152
+
153
+ # Get the status of the payment
154
+ status_response = transaction.get_status(
155
+ order_id: order_id,
156
+ amount: 1000.0,
157
+ pay_token: response.pay_token
158
+ )
159
+
160
+ pp status_response
161
+ # => #<struct OrangePaymentApi::Transaction::Status
162
+ # status="SUCCESS",
163
+ # order_id="your_order_id",
164
+ # txnid="MP241210.1234.A12345">
165
+ ```
166
+
167
+ <details>
168
+
169
+ <summary>Transaction Status and CreatedInfo Structures</summary>
170
+
171
+ The `OrangePaymentApi::Transaction::CreatedInfo` and `OrangePaymentApi::Transaction::Status` classes provide detailed information about transactions and their statuses.
172
+
173
+ ### OrangePaymentApi::Transaction::CreatedInfo
174
+
175
+ The `OrangePaymentApi::Transaction::CreatedInfo` class provides information about a newly created payment transaction.
176
+
177
+ It includes the following attributes:
178
+
179
+ - `status`: HTTP status code (always 201 for successful transaction creation).
180
+ - `message`: A message from the API about the transaction creation (always "OK" for successful creations).
181
+ - `pay_token`: A token used to check the payment status later.
182
+ - `payment_url`: The URL where the payment can be completed. Redirect users to this URL.
183
+ - `notif_token`: A token used for notification verification. Store this on your server to verify notifications from Orange.
184
+
185
+ ### OrangePaymentApi::Transaction::Status
186
+
187
+ The `OrangePaymentApi::Transaction::Status` class provides information about the status of a payment transaction.
188
+
189
+ It includes the following attributes:
190
+
191
+ - `status`: The status of the transaction. Possible values:
192
+ - `"NOT FOUND"`: All parameters of the request don't match an existing transaction.
193
+ - `"INITIATED"`: The transaction has been created but payment not yet completed.
194
+ - `"PENDING"`: The payment is being processed.
195
+ - `"EXPIRED"`: The transaction has expired.
196
+ - `"SUCCESS"`: The payment was completed successfully.
197
+ - `"FAILED"`: The payment failed.
198
+ - `order_id`: The unique identifier of the order associated with the transaction.
199
+ - `txnid`: The unique transaction identifier provided by Orange (also accessible via `transaction_id` method).
200
+
201
+ The class also provides predicate methods for checking status:
202
+ - `not_found?`: Returns true if status is "NOT FOUND"
203
+ - `initiated?`: Returns true if status is "INITIATED"
204
+ - `pending?`: Returns true if status is "PENDING"
205
+ - `expired?`: Returns true if status is "EXPIRED"
206
+ - `success?`: Returns true if status is "SUCCESS"
207
+ - `failed?`: Returns true if status is "FAILED"
208
+
209
+ </details>
210
+
211
+ ## Development
212
+
213
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
214
+
215
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
216
+
217
+ ## Contributing
218
+
219
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/orange_payment_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/orange_payment_api/blob/main/CODE_OF_CONDUCT.md).
220
+
221
+ ## License
222
+
223
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
224
+
225
+ ## Code of Conduct
226
+
227
+ Everyone interacting in the OrangePaymentApi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/orange_payment_api/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Client
5
+ Token = Struct.new(:access_token, :token_type, :scope, :expires_at, keyword_init: true) do
6
+ def expired?
7
+ Time.now >= expires_at - expires_margin
8
+ end
9
+
10
+ def valid?
11
+ !expired?
12
+ end
13
+
14
+ private
15
+
16
+ # Add a margin to the expiration time to avoid using an expired token.
17
+ def expires_margin
18
+ 5 * 60 # 5 minutes
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "client/token"
4
+ require_relative "request"
5
+
6
+ module OrangePaymentApi
7
+ class Client
8
+ include Request
9
+
10
+ ACCESS_TOKEN_URL = "https://api.orange.com/oauth/v3/token"
11
+
12
+ LANGUAGES = {
13
+ fr: "FR",
14
+ mg: "MG"
15
+ }.freeze
16
+
17
+ attr_reader :client_id, :client_secret, :language, :sandbox
18
+
19
+ # @param client_id [String] the client id provided by Orange for the application.
20
+ # Defaults to the value of the ORANGE_PAYMENT_API_CLIENT_ID environment variable.
21
+ # @param client_secret [String] the client secret provided by MVola for the application.
22
+ # Defaults to the value of the ORANGE_PAYMENT_API_CLIENT_SECRET environment variable.
23
+ # @param token [Hash, Token] a previously stored token to use for the client.
24
+ # If provided and that it is still valid, it will be used instead of fetching a new one.
25
+ def initialize(client_id: ENV["ORANGE_PAYMENT_API_CLIENT_ID"],
26
+ client_secret: ENV["ORANGE_PAYMENT_API_CLIENT_SECRET"],
27
+ language: LANGUAGES[:fr],
28
+ token: nil,
29
+ sandbox: false)
30
+ raise ArgumentError, "Client ID and Client Secret are required" if client_id.nil? || client_secret.nil?
31
+
32
+ # Warn an invalid user language if not one of the supported languages
33
+ unless LANGUAGES.key?(language.downcase.to_sym)
34
+ logger.warn "Invalid user language: #{language}. Using default language: #{LANGUAGES[:fr]}"
35
+ end
36
+
37
+ @client_id = client_id
38
+ @client_secret = client_secret
39
+ @sandbox = sandbox
40
+
41
+ @token = build_token_from(token)
42
+ @language = LANGUAGES.fetch(language.downcase.to_sym, LANGUAGES[:fr])
43
+ @mutex = Mutex.new
44
+ end
45
+
46
+ # Get the token. If the token is not valid, it will be refreshed.
47
+ def token
48
+ @mutex.synchronize do
49
+ return @token if @token&.valid?
50
+
51
+ @token = build_token_from(fetch_token)
52
+ end
53
+ end
54
+
55
+ # Force the token to be refreshed, even if it is still valid.
56
+ def token!
57
+ @token = nil
58
+ token
59
+ end
60
+
61
+ private
62
+
63
+ # This method is used to build a token object from a hash or a token object.
64
+ def build_token_from(data)
65
+ return data if data.is_a?(Token)
66
+ return unless data.is_a?(Hash)
67
+
68
+ hash = data.dup
69
+ hash[:expires_at] ||= Time.now + hash.delete(:expires_in).to_i
70
+ hash[:expires_at] = Time.parse(hash[:expires_at].to_s) if hash[:expires_at].is_a?(String)
71
+ Token.new(**hash.slice(:access_token, :token_type, :expires_at, :scope))
72
+ end
73
+
74
+ def headers
75
+ {
76
+ authorization: "Basic #{Base64.strict_encode64("#{@client_id}:#{@client_secret}")}",
77
+ accept: "application/json"
78
+ }
79
+ end
80
+
81
+ # Perform a request to fetch a new token from the MVola API.
82
+ def fetch_token
83
+ body = {
84
+ grant_type: "client_credentials"
85
+ }
86
+
87
+ response = post(ACCESS_TOKEN_URL, headers: headers, body: body)
88
+
89
+ JSON.parse(response.body, symbolize_names: true)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Error < StandardError; end
5
+
6
+ class BadRequestError < Error; end
7
+
8
+ class UnauthorizedError < Error; end
9
+
10
+ class NotFoundError < Error; end
11
+
12
+ class ServerError < Error; end
13
+
14
+ class ApiError < Error; end
15
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "forwardable"
5
+
6
+ module OrangePaymentApi
7
+ module Request
8
+ extend Forwardable
9
+
10
+ def_delegators OrangePaymentApi, :logger
11
+
12
+ # Method to perform a POST request
13
+ # @param url [String] the URL to perform the request
14
+ # @param args [Hash] the arguments to pass to the request
15
+ # @option args [Hash] :params the parameters to pass to the request
16
+ # @option args [Hash] :headers the headers to pass to the request
17
+ # @option args [Hash] :body the body to pass to the request
18
+ # @option args [Hash] :json the JSON to pass to the request body. Replace the body if present
19
+ def post(url, args = {})
20
+ uri = URI.parse(url)
21
+ headers = args.delete(:headers) || {}
22
+ headers["Content-Type"] = "application/json" if args.key?(:json)
23
+
24
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
25
+
26
+ request.form_data = args[:body] if args[:body]
27
+ if (json = args.delete(:json))
28
+ request.body = json.to_json
29
+ end
30
+
31
+ puts("POST #{url} #{args}")
32
+ response = build_http(uri, args).request(request)
33
+ puts("Response: #{response.code} #{response.body.inspect}")
34
+
35
+ handle_error(response)
36
+
37
+ response
38
+ end
39
+
40
+ private
41
+
42
+ def build_http(uri, args)
43
+ uri.query = URI.encode_www_form(args[:params]) if args[:params]
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true
46
+
47
+ http
48
+ end
49
+
50
+ def handle_error(response)
51
+ case response.code.to_i
52
+ when 200..299
53
+ # Do nothing
54
+ when 400
55
+ raise OrangePaymentApi::BadRequestError, response.body
56
+ when 401
57
+ raise OrangePaymentApi::UnauthorizedError, response.body
58
+ when 404
59
+ raise OrangePaymentApi::NotFoundError, response.body
60
+ when 500..599
61
+ raise OrangePaymentApi::ServerError, response.body
62
+ else
63
+ raise OrangePaymentApi::ApiError, response.body
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Transaction
5
+ # Struct to hold information about a created transaction.
6
+ # This is aimed to be used internally and not exposed to the public API.
7
+ # Status is always `201` returned by the API when a transaction is created successfully.
8
+ # Attributes:
9
+ # - status: HTTP status code (always 201 for created transactions)
10
+ # - message: A message from the API about the transaction creation. Always "OK" for successful creations.
11
+ # - pay_token: A token used to proceed with the payment.
12
+ # - payment_url: The URL where the payment can be completed. Clients should redirect users to this URL.
13
+ # - notif_token: A token used for notification purposes. You would want to store this in your server, so you can verify notifications from Orange.
14
+ CreatedInfo = Struct.new(:status, :message, :pay_token, :payment_url, :notif_token, keyword_init: true) do
15
+ def created?
16
+ status == 201
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Transaction
5
+ NOT_FOUND = "NOT FOUND"
6
+ INITIATED = "INITIATED"
7
+ PENDING = "PENDING"
8
+ EXPIRED = "EXPIRED"
9
+ SUCCESS = "SUCCESS"
10
+ FAILED = "FAILED"
11
+
12
+ # Response's object of the transaction status
13
+ # Attributes:
14
+ # - status: Status of the transaction.
15
+ # Possible values: "NOT FOUND", "INITIATED", "PENDING", "EXPIRED", "SUCCESS", "FAILED".
16
+ # The status 'NOT FOUND' is returned when all parameters of the request don't match an existing transaction.
17
+ # - order_id: The unique identifier of the order associated with the transaction.
18
+ # - txnid: The unique identifier of the transaction provided by Orange.
19
+ Status = Struct.new(:status, :order_id, :txnid, keyword_init: true) do
20
+ # Define predicate methods for each possible status
21
+ # e.g., initiated?, pending?, success?, etc.
22
+ # Returns true if the transaction status matches the method name.
23
+ [NOT_FOUND, INITIATED, PENDING, EXPIRED, SUCCESS, FAILED].each do |s|
24
+ define_method("#{s.downcase.tr(" ", "_")}?") do
25
+ status == s
26
+ end
27
+ end
28
+
29
+ # This field may be empty depending on the status.
30
+ def transaction_id
31
+ txnid
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "client"
4
+ require_relative "transaction/status"
5
+ require_relative "transaction/created_info"
6
+
7
+ module OrangePaymentApi
8
+ class Transaction
9
+ include Request
10
+
11
+ API_VERSION = "v1"
12
+ BASE_URL = "https://api.orange.com/"
13
+ DEV_ENDPOINT_URL = "#{BASE_URL}orange-money-webpay/dev/#{API_VERSION}"
14
+ PROD_ENDPOINT = "#{BASE_URL}orange-money-webpay/#{API_VERSION}"
15
+ DEFAULT_CURRENCY = "MGA"
16
+ DEV_CURRENCY = "OUV"
17
+
18
+ attr_reader :client, :endpoint
19
+
20
+ def initialize(client: nil)
21
+ @client = client || Client.new
22
+ @endpoint = @client.sandbox ? DEV_ENDPOINT_URL : PROD_ENDPOINT
23
+ end
24
+
25
+ # Initiate a payment transaction.
26
+ # @param amount [Integer] The amount to be paid in the currency.
27
+ # @param merchant_key [String] The merchant key provided by Orange.
28
+ # @param order_id [String] The unique order ID for the transaction. up to 30 chars alphanumeric.
29
+ # @param return_url [String] The URL to redirect the user after payment completion.
30
+ # @param cancel_url [String] The URL to redirect the user if the payment is canceled.
31
+ # @param notif_url [String] The URL for server-to-server notification of payment status.
32
+ # @param currency [String] The currency code for the transaction. Default to MGA (Malagasy Ariary).
33
+ # @param language [String] The language code for the transaction. Default to the client instance language.
34
+ # @param reference [String, nil] An optional reference string for the transaction.
35
+ #
36
+ # @example
37
+ # {
38
+ # "merchant_key": "a86b2087",
39
+ # "order_id": "MY_ORDER_ID_08082105_0023457",
40
+ # "currency": "Ar",
41
+ # "amount": 1200,
42
+ # "return_url": "http://myvirtualshop.webnode.es",
43
+ # "cancel_url": "http://myvirtualshop.webnode.es/txncncld/",
44
+ # "notif_url": "http://www.merchant-example2.org/notif",
45
+ # "lang": "fr"
46
+ # "reference": "ref Merchant"
47
+ # }
48
+ def initiate_payment!(amount:,
49
+ merchant_key:,
50
+ order_id:,
51
+ currency: DEFAULT_CURRENCY,
52
+ return_url:,
53
+ cancel_url:,
54
+ notif_url:,
55
+ language: client.language,
56
+ reference: nil)
57
+ ensure_valid_order_id!(order_id)
58
+ ensure_valid_url!(return_url)
59
+ ensure_valid_url!(cancel_url)
60
+ ensure_valid_url!(notif_url)
61
+
62
+ request_currency = client.sandbox ? DEV_CURRENCY : currency
63
+
64
+ payload = {
65
+ amount: amount.to_f.round(2),
66
+ currency: request_currency,
67
+ merchant_key: merchant_key,
68
+ order_id: order_id,
69
+ return_url: return_url,
70
+ cancel_url: cancel_url,
71
+ notif_url: notif_url,
72
+ lang: language,
73
+ reference: reference
74
+ }
75
+
76
+ url = url_for("webpayment")
77
+ logger.debug "Initiating payment with payload: #{payload}"
78
+ response = post(url, json: payload, headers: headers)
79
+ logger.debug "Payment initiated. Response: #{response.body}"
80
+
81
+ parsed_body = JSON.parse(response.body, symbolize_names: true)
82
+ CreatedInfo.new(**parsed_body.slice(:status, :message, :pay_token, :payment_url, :notif_token))
83
+ end
84
+
85
+ # Get the status of a payment using the server correlation ID.
86
+ # This can be used to poll the status of a payment.
87
+ def get_status(order_id:, amount:, pay_token:)
88
+ url = url_for("transactionstatus")
89
+
90
+ payload = {
91
+ order_id: order_id,
92
+ amount: amount.to_f.round(2),
93
+ pay_token: pay_token
94
+ }
95
+ response = post(url, json: payload, headers: headers)
96
+
97
+ parsed_body = JSON.parse(response.body, symbolize_names: true)
98
+ Status.new(**parsed_body.slice(:status, :order_id, :txnid))
99
+ end
100
+
101
+ private
102
+
103
+ # Generate the URL for the given path by joining the base URL and the endpoint.
104
+ def url_for(path = "")
105
+ safe_path = path.gsub(/^\//, "") # Remove the starting slash from the path (if any) to avoid incorrect URL generation
106
+ Pathname.new(endpoint).join(safe_path).to_s
107
+ end
108
+
109
+ def headers
110
+ {
111
+ "Authorization" => "Bearer #{client.token.access_token}",
112
+ "Accept" => "application/json",
113
+ "Content-Type" => "application/json",
114
+ "Cache-Control" => "no-cache"
115
+ }
116
+ end
117
+
118
+ def ensure_valid_order_id!(order_id)
119
+ raise ArgumentError, "order_id is required" if order_id.nil?
120
+ raise ArgumentError, "order_id must be 30 characters long maximum" if order_id.size > 30
121
+
122
+ order_id_regexp = /\A[\w\-\.]+\z/
123
+
124
+ unless order_id.match?(order_id_regexp)
125
+ raise ArgumentError, "order_id contains invalid characters. " \
126
+ "Allowed characters are alphanumeric, hyphen (-), underscore (_), dot (.), space and comma (,)."
127
+ end
128
+ end
129
+
130
+ def ensure_valid_url!(url)
131
+ uri = URI.parse(url)
132
+ unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
133
+ raise ArgumentError, "Invalid URL format: #{url}"
134
+ end
135
+ rescue URI::InvalidURIError
136
+ raise ArgumentError, "Invalid URL format: #{url}"
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "base64"
5
+ require "time"
6
+ require "json"
7
+ require_relative "orange_payment_api/version"
8
+ require_relative "orange_payment_api/errors"
9
+ require_relative "orange_payment_api/client"
10
+ require_relative "orange_payment_api/transaction"
11
+
12
+ module OrangePaymentApi
13
+ class << self
14
+ attr_accessor :logger
15
+ end
16
+
17
+ # Set up a default logger
18
+ self.logger = Logger.new($stdout) # Logs to the console
19
+ logger.level = Logger::INFO # Default log level
20
+ end
@@ -0,0 +1,18 @@
1
+ module OrangePaymentApi
2
+ class Client
3
+ class Token
4
+ attr_accessor access_token: String
5
+ attr_accessor token_type: String
6
+ attr_accessor scope: String
7
+ attr_accessor expires_at: Time
8
+
9
+ def initialize: (access_token: String, token_type: String, expires_at: Time) -> void
10
+ def expired?: () -> bool
11
+ def valid?: () -> bool
12
+
13
+ private
14
+
15
+ def expires_margin: () -> Integer
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module OrangePaymentApi
2
+ class Client
3
+ include Request
4
+
5
+ ACCESS_TOKEN_URL: String
6
+ LANGUAGES: HashWithIndifferentAccess[Symbol, String]
7
+
8
+ @client_id: String
9
+ @client_secret: String
10
+ @language: Symbol
11
+ @token: Token?
12
+ @sandbox: bool
13
+ @mutex: Mutex
14
+
15
+ attr_reader client_id: String
16
+ attr_reader client_secret: String
17
+ attr_reader language: Symbol | String
18
+ attr_reader sandbox: bool
19
+
20
+ def initialize: (?client_id: String,
21
+ ?client_secret: String,
22
+ ?language: Symbol | String,
23
+ ?token: Token?,
24
+ ?sandbox: bool) -> void
25
+
26
+ def token: () -> Token
27
+
28
+ def token!: () -> Token
29
+
30
+ private
31
+
32
+ def build_token_from: (token_data: Hash[Symbol, String]) -> Token
33
+
34
+ def headers: () -> Hash[String, String]
35
+
36
+ def fetch_token: () -> Hash[Symbol, String]
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Error < StandardError
5
+ end
6
+
7
+ class BadRequestError < Error
8
+ end
9
+
10
+ class UnauthorizedError < Error
11
+ end
12
+
13
+ class NotFoundError < Error
14
+ end
15
+
16
+ class ServerError < Error
17
+ end
18
+
19
+ class ApiError < Error
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module OrangePaymentApi
2
+ module Request
3
+ def get: (String url, ?::Hash[untyped, untyped] args) -> Net::HTTPResponse
4
+
5
+ def post: (String url, ?::Hash[untyped, untyped] args) -> Net::HTTPResponse
6
+
7
+ private
8
+
9
+ def build_http: (URI uri, Hash[untyped, untyped] args) -> Net::HTTP
10
+
11
+ def handle_error: (Net::HTTPResponse response) -> void
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Transaction
5
+ class CreatedInfo
6
+ attr_accessor status: String
7
+ attr_accessor message: String
8
+ attr_accessor pay_token: String
9
+ attr_accessor payment_url: String
10
+ attr_accessor notif_token: String
11
+
12
+ def initialize: (
13
+ status: String,
14
+ message: String,
15
+ pay_token: String,
16
+ payment_url: String,
17
+ notif_token: String) -> void
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Transaction
5
+ class Status
6
+ attr_accessor status: String
7
+ attr_accessor order_id: String
8
+ attr_accessor txnid: String
9
+
10
+ NOT_FOUND: String
11
+ INITIATED: String
12
+ PENDING: String
13
+ EXPIRED: String
14
+ SUCCESS: String
15
+ FAILED: String
16
+
17
+ def initialize: (
18
+ status: String,
19
+ order_id: String,
20
+ txnid: String
21
+ ) -> void
22
+
23
+ def not_found?: () -> bool
24
+ def initiated?: () -> bool
25
+ def pending?: () -> bool
26
+ def expired?: () -> bool
27
+ def success?: () -> bool
28
+ def failed?: () -> bool
29
+ def transaction_id: () -> String
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangePaymentApi
4
+ class Transaction
5
+ include Request
6
+
7
+ API_VERSION: String
8
+ BASE_URL: String
9
+ DEV_ENDPOINT_URL: String
10
+ PROD_ENDPOINT: String
11
+ DEFAULT_CURRENCY: String
12
+ DEV_CURRENCY: String
13
+
14
+ @client: Client
15
+ @endpoint: String
16
+ @sandbox: bool
17
+
18
+ attr_reader client: Client
19
+ attr_reader endpoint: String
20
+ attr_reader sandbox: bool
21
+
22
+ def initialize: (?client: Client?, ?sandbox: bool) -> void
23
+
24
+ def initiate_payment!: (
25
+ amount: Float | Integer,
26
+ merchant_key: String,
27
+ order_id: String,
28
+ return_url: String,
29
+ cancel_url: String,
30
+ notif_url: String,
31
+ ?currency: String,
32
+ ?language: String,
33
+ ?reference: String?
34
+ ) -> CreatedInfo
35
+
36
+ def get_status: (
37
+ order_id: String,
38
+ amount: Float | Integer,
39
+ pay_token: String
40
+ ) -> Status
41
+
42
+ private
43
+
44
+ def url_for: (?String path) -> String
45
+
46
+ def headers: () -> Hash[String, String]
47
+
48
+ def ensure_valid_order_id!: (String order_id) -> void
49
+
50
+ def ensure_valid_url!: (String url) -> void
51
+ end
52
+ end
@@ -0,0 +1,8 @@
1
+ module OrangePaymentApi
2
+ VERSION: String
3
+
4
+ @logger: Logger
5
+
6
+ def self.logger: () -> Logger
7
+ def self.logger=: (Logger logger) -> void
8
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orange_payment_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Kassam
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Ruby client for the Orange Payment API
13
+ email:
14
+ - kassam.housseny@antsena.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".idea/copilot.data.migration.ask2agent.xml"
20
+ - ".idea/modules.xml"
21
+ - ".idea/orange_payment_api.iml"
22
+ - ".idea/vcs.xml"
23
+ - ".idea/workspace.xml"
24
+ - ".overcommit.yml"
25
+ - CHANGELOG.md
26
+ - CODE_OF_CONDUCT.md
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - lib/orange_payment_api.rb
31
+ - lib/orange_payment_api/client.rb
32
+ - lib/orange_payment_api/client/token.rb
33
+ - lib/orange_payment_api/errors.rb
34
+ - lib/orange_payment_api/request.rb
35
+ - lib/orange_payment_api/transaction.rb
36
+ - lib/orange_payment_api/transaction/created_info.rb
37
+ - lib/orange_payment_api/transaction/status.rb
38
+ - lib/orange_payment_api/version.rb
39
+ - sig/orange_payment_api.rbs
40
+ - sig/orange_payment_api/client.rbs
41
+ - sig/orange_payment_api/client/token.rbs
42
+ - sig/orange_payment_api/errors.rbs
43
+ - sig/orange_payment_api/request.rbs
44
+ - sig/orange_payment_api/transaction.rbs
45
+ - sig/orange_payment_api/transaction/created_info.rbs
46
+ - sig/orange_payment_api/transaction/status.rbs
47
+ homepage: https://github.com/AnTsena/orange_payment_api
48
+ licenses:
49
+ - MIT
50
+ metadata:
51
+ homepage_uri: https://github.com/AnTsena/orange_payment_api
52
+ source_code_uri: https://github.com/AnTsena/orange_payment_api
53
+ changelog_uri: https://github.com/AnTsena/orange_payment_api/CHANGELOG.md
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.7.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 4.0.6
69
+ specification_version: 4
70
+ summary: A Ruby client for the Orange Payment API
71
+ test_files: []