paygate_pk 0.2.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -3
  3. data/CHANGELOG.md +213 -0
  4. data/Gemfile.lock +46 -5
  5. data/README.md +291 -64
  6. data/lib/paygate_pk/coercions.rb +64 -0
  7. data/lib/paygate_pk/config.rb +113 -25
  8. data/lib/paygate_pk/contracts/access_token.rb +8 -2
  9. data/lib/paygate_pk/contracts/callback_event.rb +38 -0
  10. data/lib/paygate_pk/contracts/charge_result.rb +52 -0
  11. data/lib/paygate_pk/contracts/inquiry_result.rb +58 -0
  12. data/lib/paygate_pk/contracts/redirect_request.rb +30 -0
  13. data/lib/paygate_pk/easy_paisa/client.rb +64 -0
  14. data/lib/paygate_pk/easy_paisa/endpoints.rb +34 -0
  15. data/lib/paygate_pk/easy_paisa/inquiry.rb +87 -0
  16. data/lib/paygate_pk/easy_paisa/mobile_account.rb +123 -0
  17. data/lib/paygate_pk/easy_paisa/otc.rb +146 -0
  18. data/lib/paygate_pk/easy_paisa.rb +21 -0
  19. data/lib/paygate_pk/errors.rb +16 -3
  20. data/lib/paygate_pk/http/client.rb +84 -71
  21. data/lib/paygate_pk/pay_fast/auth.rb +79 -0
  22. data/lib/paygate_pk/pay_fast/callback.rb +92 -0
  23. data/lib/paygate_pk/pay_fast/endpoints.rb +38 -0
  24. data/lib/paygate_pk/pay_fast/redirect.rb +227 -0
  25. data/lib/paygate_pk/pay_fast.rb +19 -0
  26. data/lib/paygate_pk/rails/railtie.rb +19 -0
  27. data/lib/paygate_pk/rails/view_helpers.rb +159 -0
  28. data/lib/paygate_pk/util/credentials.rb +27 -0
  29. data/lib/paygate_pk/util/signature/pay_fast.rb +25 -0
  30. data/lib/paygate_pk/version.rb +1 -1
  31. data/lib/paygate_pk.rb +54 -18
  32. metadata +34 -32
  33. data/lib/paygate_pk/contracts/bearer_token.rb +0 -18
  34. data/lib/paygate_pk/contracts/hosted_checkout.rb +0 -8
  35. data/lib/paygate_pk/contracts/instrument.rb +0 -10
  36. data/lib/paygate_pk/contracts/webhook_event.rb +0 -24
  37. data/lib/paygate_pk/providers/pay_fast/auth.rb +0 -61
  38. data/lib/paygate_pk/providers/pay_fast/checkout.rb +0 -157
  39. data/lib/paygate_pk/providers/pay_fast/client.rb +0 -53
  40. data/lib/paygate_pk/providers/pay_fast/tokenization/instrument.rb +0 -63
  41. data/lib/paygate_pk/providers/pay_fast/tokenization/token.rb +0 -65
  42. data/lib/paygate_pk/providers/pay_fast/webhook.rb +0 -74
  43. data/lib/paygate_pk/util/html.rb +0 -42
  44. data/lib/paygate_pk/util/signature.rb +0 -18
  45. data/paygate_pk.gemspec +0 -46
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaygatePk
4
+ # PayFast integration.
5
+ #
6
+ # Public API (1.0):
7
+ #
8
+ # PaygatePk::PayFast::Redirect.build(...) # => Contracts::RedirectRequest
9
+ # PaygatePk::PayFast::Callback.verify!(p) # => Contracts::CallbackEvent
10
+ #
11
+ # Internal helpers (Auth, Endpoints) are loaded but not part of the
12
+ # advertised surface — they may change without a major version bump.
13
+ module PayFast
14
+ # Shortcut to the provider-scoped config block.
15
+ def self.config
16
+ PaygatePk.config.pay_fast
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+ require_relative "view_helpers"
5
+
6
+ module PaygatePk
7
+ module Rails
8
+ # Wires ViewHelpers into ActionView when the gem boots inside a
9
+ # Rails app. Loaded conditionally from lib/paygate_pk.rb so that
10
+ # non-Rails consumers don't have to install Rails to use the gem.
11
+ class Railtie < ::Rails::Railtie
12
+ initializer "paygate_pk.view_helpers" do
13
+ ActiveSupport.on_load(:action_view) do
14
+ include PaygatePk::Rails::ViewHelpers
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaygatePk
4
+ module Rails
5
+ # Action View helpers, auto-included into ActionView::Base by the
6
+ # Railtie when the gem boots inside a Rails app.
7
+ module ViewHelpers
8
+ DEFAULT_FORM_ID = "paygate-pk-redirect-form"
9
+ SUBMIT_LABEL = "Pay now"
10
+
11
+ OTC_VOUCHER_SUCCESS_CLASS = "paygate-pk-otc-voucher rounded-2xl border border-emerald-200 bg-emerald-50 p-6"
12
+ OTC_VOUCHER_FAILURE_CLASS = "paygate-pk-otc-voucher paygate-pk-otc-voucher--failed " \
13
+ "rounded-2xl border border-rose-200 bg-rose-50 p-6"
14
+
15
+ # Renders the auto-submitting redirect form for any provider that
16
+ # produces a Contracts::RedirectRequest.
17
+ #
18
+ # <%= paygate_pk_redirect_form(@redirect) %>
19
+ #
20
+ # Options:
21
+ # autosubmit: true (default) — emits a script tag that submits
22
+ # the form on page load. Set to false to render a
23
+ # visible "Pay now" button instead.
24
+ # html: { id:, class:, data: { ... }, ... } — passed
25
+ # straight to the <form> tag. `id` falls back to
26
+ # "paygate-pk-redirect-form".
27
+ # submit_label: button label when autosubmit is false.
28
+ def paygate_pk_redirect_form(redirect, autosubmit: true, html: {}, submit_label: SUBMIT_LABEL)
29
+ form_id = html[:id] || DEFAULT_FORM_ID
30
+ form_html = render_form(redirect, form_id, html, autosubmit, submit_label)
31
+ return form_html unless autosubmit
32
+
33
+ form_html + autosubmit_script(form_id)
34
+ end
35
+
36
+ # Renders a printable Easypaisa OTC voucher from a
37
+ # Contracts::ChargeResult (from PaygatePk::EasyPaisa::OTC.create).
38
+ #
39
+ # <%= paygate_pk_otc_voucher(@voucher) %>
40
+ #
41
+ # Options:
42
+ # html: { class:, ... } — outer container overrides
43
+ # title: heading text (default "Pay at any Easypaisa shop")
44
+ # instructions: array of bullet strings; falls back to a sane
45
+ # default that mentions the expiry time
46
+ def paygate_pk_otc_voucher(result, html: {}, title: "Pay at any Easypaisa shop", instructions: nil)
47
+ return otc_voucher_failure(result, html) unless result.success? && result.payment_token.present?
48
+
49
+ container_attrs = otc_voucher_attrs(html, success: true)
50
+ body = otc_voucher_success_body(result, title: title,
51
+ instructions: instructions || default_otc_instructions(result))
52
+
53
+ content_tag(:div, body, container_attrs)
54
+ end
55
+
56
+ private
57
+
58
+ def render_form(redirect, form_id, html, autosubmit, submit_label)
59
+ attrs = {
60
+ id: form_id,
61
+ action: redirect.action_url,
62
+ method: redirect.http_method.to_s,
63
+ accept_charset: "UTF-8"
64
+ }.merge(html.except(:id))
65
+
66
+ content_tag(:form, attrs) do
67
+ hidden = redirect.fields.map do |name, value|
68
+ tag.input(type: "hidden", name: name.to_s, value: value.to_s)
69
+ end
70
+ submit = autosubmit ? "".html_safe : tag.button(submit_label, type: "submit")
71
+ safe_join(hidden + [submit])
72
+ end
73
+ end
74
+
75
+ def autosubmit_script(form_id)
76
+ javascript_tag(
77
+ "document.getElementById(#{form_id.to_json}).submit();",
78
+ nonce: respond_to?(:content_security_policy_nonce) ? content_security_policy_nonce : nil
79
+ )
80
+ end
81
+
82
+ # ── OTC voucher rendering ─────────────────────────────────────
83
+
84
+ def otc_voucher_attrs(html, success:)
85
+ default_class = success ? OTC_VOUCHER_SUCCESS_CLASS : OTC_VOUCHER_FAILURE_CLASS
86
+ html.merge(class: [default_class, html[:class]].compact.join(" "))
87
+ end
88
+
89
+ def otc_voucher_success_body(result, title:, instructions:)
90
+ safe_join([
91
+ content_tag(:h3, title, class: "text-base font-bold text-slate-900 mb-4"),
92
+ otc_voucher_token_block(result),
93
+ otc_voucher_meta(result),
94
+ otc_voucher_instructions(instructions)
95
+ ])
96
+ end
97
+
98
+ def otc_voucher_token_block(result)
99
+ content_tag(:div, class: "rounded-xl bg-white border border-emerald-200 px-5 py-4 mb-4 text-center") do
100
+ safe_join([
101
+ content_tag(:p, "Payment token",
102
+ class: "text-xs font-semibold uppercase tracking-wide text-slate-500"),
103
+ content_tag(:p, result.payment_token,
104
+ class: "mt-1 font-mono text-2xl font-bold tracking-widest text-slate-900")
105
+ ])
106
+ end
107
+ end
108
+
109
+ def otc_voucher_meta(result)
110
+ rows = []
111
+ rows << ["Amount", "PKR #{result.amount}"]
112
+ rows << ["Mobile", result.customer[:msisdn]] if result.customer[:msisdn].present?
113
+ rows << ["Expires", otc_voucher_format_time(result.expires_at)] if result.expires_at
114
+ rows << ["Order", result.basket_id]
115
+
116
+ content_tag(:dl, class: "grid grid-cols-2 gap-y-2 text-sm mb-4") do
117
+ safe_join(rows.flat_map do |label, value|
118
+ [
119
+ content_tag(:dt, label, class: "text-slate-500"),
120
+ content_tag(:dd, value, class: "text-right font-semibold text-slate-900")
121
+ ]
122
+ end)
123
+ end
124
+ end
125
+
126
+ def otc_voucher_instructions(instructions)
127
+ content_tag(:ul, class: "list-disc pl-5 space-y-1 text-xs text-slate-600") do
128
+ safe_join(instructions.map { |line| content_tag(:li, line) })
129
+ end
130
+ end
131
+
132
+ def default_otc_instructions(result)
133
+ expiry = result.expires_at ? otc_voucher_format_time(result.expires_at) : nil
134
+ [
135
+ "Visit any Easypaisa shop with the token above.",
136
+ "Pay PKR #{result.amount} in cash and quote your token to the agent.",
137
+ expiry ? "Pay before #{expiry} — the token expires after that." : nil
138
+ ].compact
139
+ end
140
+
141
+ def otc_voucher_failure(result, html)
142
+ container_attrs = otc_voucher_attrs(html, success: false)
143
+ message = result.response_message.presence || "Easypaisa did not issue a token for this order."
144
+ body = safe_join([
145
+ content_tag(:h3, "Voucher unavailable", class: "text-base font-bold text-rose-900 mb-2"),
146
+ content_tag(:p, message, class: "text-sm text-rose-700")
147
+ ])
148
+ content_tag(:div, body, container_attrs)
149
+ end
150
+
151
+ def otc_voucher_format_time(value)
152
+ return value if value.is_a?(String)
153
+ return nil unless value.respond_to?(:strftime)
154
+
155
+ value.strftime("%d %b %Y, %I:%M %p")
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module PaygatePk
6
+ module Util
7
+ # Header-credential helpers used by REST providers that authenticate
8
+ # with a static username/password pair.
9
+ #
10
+ # Easypaisa: header key "Credentials", value is Base64Strict of
11
+ # "username:password" -- HTTP-Basic style but WITHOUT the literal
12
+ # "Basic " scheme prefix.
13
+ module Credentials
14
+ module_function
15
+
16
+ # Returns Base64Strict("user:pass"). Raises if either is blank
17
+ # -- empty credentials would silently authenticate as a different
18
+ # principal and produce baffling 401s downstream.
19
+ def basic(username, password)
20
+ raise PaygatePk::ConfigurationError, "username is required" if Coercions.blank?(username)
21
+ raise PaygatePk::ConfigurationError, "password is required" if Coercions.blank?(password)
22
+
23
+ Base64.strict_encode64("#{username}:#{password}")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module PaygatePk
6
+ module Util
7
+ module Signature
8
+ # PayFast IPN/return validation_hash computation.
9
+ #
10
+ # Per Merchant Integration Guide v2.3 §3.2.3:
11
+ # validation_hash = SHA256("basket_id|secured_key|merchant_id|err_code")
12
+ #
13
+ # Returned as a hex digest. Compared in constant time on the receiving
14
+ # side via PaygatePk::Util::Security.secure_compare.
15
+ module PayFast
16
+ SEPARATOR = "|"
17
+
18
+ def self.validation_hash(basket_id:, merchant_secret_key:, merchant_id:, payfast_err_code:)
19
+ data = [basket_id, merchant_secret_key, merchant_id, payfast_err_code].join(SEPARATOR)
20
+ OpenSSL::Digest::SHA256.hexdigest(data)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaygatePk
4
- VERSION = "0.2.3"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/paygate_pk.rb CHANGED
@@ -2,37 +2,73 @@
2
2
 
3
3
  require_relative "paygate_pk/version"
4
4
  require_relative "paygate_pk/errors"
5
+ require_relative "paygate_pk/coercions"
5
6
  require_relative "paygate_pk/config"
7
+
8
+ require_relative "paygate_pk/util/security"
9
+ require_relative "paygate_pk/util/signature/pay_fast"
10
+ require_relative "paygate_pk/util/credentials"
11
+
6
12
  require_relative "paygate_pk/http/client"
7
13
 
8
- # Contracts used by the endpoint
9
14
  require_relative "paygate_pk/contracts/access_token"
10
- require_relative "paygate_pk/contracts/hosted_checkout"
11
- require_relative "paygate_pk/contracts/bearer_token"
12
- require_relative "paygate_pk/contracts/instrument"
13
- require_relative "paygate_pk/contracts/webhook_event"
14
-
15
- # PayFast
16
- require_relative "paygate_pk/providers/pay_fast/client"
17
- require_relative "paygate_pk/providers/pay_fast/auth"
18
- require_relative "paygate_pk/providers/pay_fast/checkout"
19
- require_relative "paygate_pk/providers/pay_fast/webhook"
20
- require_relative "paygate_pk/providers/pay_fast/tokenization/token"
21
-
22
- require_relative "paygate_pk/util/html"
23
- require_relative "paygate_pk/util/signature"
24
- require_relative "paygate_pk/util/security"
15
+ require_relative "paygate_pk/contracts/redirect_request"
16
+ require_relative "paygate_pk/contracts/callback_event"
17
+ require_relative "paygate_pk/contracts/charge_result"
18
+ require_relative "paygate_pk/contracts/inquiry_result"
19
+
20
+ require_relative "paygate_pk/pay_fast"
21
+ require_relative "paygate_pk/pay_fast/endpoints"
22
+ require_relative "paygate_pk/pay_fast/auth"
23
+ require_relative "paygate_pk/pay_fast/redirect"
24
+ require_relative "paygate_pk/pay_fast/callback"
25
25
 
26
- # Main module for PaygatePk
26
+ require_relative "paygate_pk/easy_paisa"
27
+ require_relative "paygate_pk/easy_paisa/endpoints"
28
+ require_relative "paygate_pk/easy_paisa/client"
29
+ require_relative "paygate_pk/easy_paisa/mobile_account"
30
+ require_relative "paygate_pk/easy_paisa/otc"
31
+ require_relative "paygate_pk/easy_paisa/inquiry"
32
+
33
+ require_relative "paygate_pk/rails/railtie" if defined?(Rails::Railtie)
34
+
35
+ # Unified Ruby/Rails client for Pakistani payment gateways.
36
+ #
37
+ # PaygatePk.configure do |c|
38
+ # # PayFast (hosted checkout / redirection)
39
+ # c.pay_fast.environment = :sandbox
40
+ # c.pay_fast.merchant_id = ENV["PAYFAST_MERCHANT_ID"]
41
+ # c.pay_fast.secured_key = ENV["PAYFAST_SECURED_KEY"]
42
+ # c.pay_fast.merchant_name = "Acme Store"
43
+ #
44
+ # # Easypaisa (REST APIs)
45
+ # c.easy_paisa.environment = :sandbox
46
+ # c.easy_paisa.username = ENV["EASYPAISA_USERNAME"]
47
+ # c.easy_paisa.password = ENV["EASYPAISA_PASSWORD"]
48
+ # c.easy_paisa.store_id = ENV["EASYPAISA_STORE_ID"]
49
+ # end
50
+ #
51
+ # redirect = PaygatePk::PayFast::Redirect.build(...)
52
+ # event = PaygatePk::PayFast::Callback.verify!(request.parameters)
53
+ # result = PaygatePk::EasyPaisa::MobileAccount.charge(...)
54
+ # voucher = PaygatePk::EasyPaisa::OTC.create(...)
55
+ # status = PaygatePk::EasyPaisa::Inquiry.fetch(...)
27
56
  module PaygatePk
28
57
  class << self
29
58
  def configure
30
59
  yield(config)
31
60
  config.freeze!
61
+ config
32
62
  end
33
63
 
34
64
  def config
35
- @config ||= PaygatePk::Config.new
65
+ @config ||= Config.new
66
+ end
67
+
68
+ # Test/dev helper. Discards the current (possibly frozen) config so
69
+ # that a fresh PaygatePk.configure call can re-seed it.
70
+ def reset_config!
71
+ @config = Config.new
36
72
  end
37
73
  end
38
74
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paygate_pk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Talha Junaid
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-10 00:00:00.000000000 Z
11
+ date: 2026-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -53,25 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: nokogiri
56
+ name: actionview
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.16'
62
- - - "<"
63
- - !ruby/object:Gem::Version
64
- version: '2.0'
65
- type: :runtime
61
+ version: '7.0'
62
+ type: :development
66
63
  prerelease: false
67
64
  version_requirements: !ruby/object:Gem::Requirement
68
65
  requirements:
69
66
  - - ">="
70
67
  - !ruby/object:Gem::Version
71
- version: '1.16'
72
- - - "<"
73
- - !ruby/object:Gem::Version
74
- version: '2.0'
68
+ version: '7.0'
75
69
  - !ruby/object:Gem::Dependency
76
70
  name: byebug
77
71
  requirement: !ruby/object:Gem::Requirement
@@ -156,9 +150,10 @@ dependencies:
156
150
  - - ">="
157
151
  - !ruby/object:Gem::Version
158
152
  version: '0'
159
- description: |-
160
- Provider-agnostic Ruby/Rails client for PayFast: checkout, webhooks/IPN verification,
161
- tokenized & recurring payments.
153
+ description: PaygatePk is a provider-agnostic Ruby/Rails client for payment gateways
154
+ operating in Pakistan. 1.0 ships PayFast hosted-checkout (redirection) and callback
155
+ verification with a one-line Rails view helper. Easypaisa REST (MA/OTC/Inquiry)
156
+ lands in 1.1.
162
157
  email:
163
158
  - talhajunaid65@gmail.com
164
159
  executables: []
@@ -174,43 +169,50 @@ files:
174
169
  - README.md
175
170
  - Rakefile
176
171
  - lib/paygate_pk.rb
172
+ - lib/paygate_pk/coercions.rb
177
173
  - lib/paygate_pk/config.rb
178
174
  - lib/paygate_pk/contracts/access_token.rb
179
- - lib/paygate_pk/contracts/bearer_token.rb
180
- - lib/paygate_pk/contracts/hosted_checkout.rb
181
- - lib/paygate_pk/contracts/instrument.rb
182
- - lib/paygate_pk/contracts/webhook_event.rb
175
+ - lib/paygate_pk/contracts/callback_event.rb
176
+ - lib/paygate_pk/contracts/charge_result.rb
177
+ - lib/paygate_pk/contracts/inquiry_result.rb
178
+ - lib/paygate_pk/contracts/redirect_request.rb
179
+ - lib/paygate_pk/easy_paisa.rb
180
+ - lib/paygate_pk/easy_paisa/client.rb
181
+ - lib/paygate_pk/easy_paisa/endpoints.rb
182
+ - lib/paygate_pk/easy_paisa/inquiry.rb
183
+ - lib/paygate_pk/easy_paisa/mobile_account.rb
184
+ - lib/paygate_pk/easy_paisa/otc.rb
183
185
  - lib/paygate_pk/errors.rb
184
186
  - lib/paygate_pk/http/client.rb
185
- - lib/paygate_pk/providers/pay_fast/auth.rb
186
- - lib/paygate_pk/providers/pay_fast/checkout.rb
187
- - lib/paygate_pk/providers/pay_fast/client.rb
188
- - lib/paygate_pk/providers/pay_fast/tokenization/instrument.rb
189
- - lib/paygate_pk/providers/pay_fast/tokenization/token.rb
190
- - lib/paygate_pk/providers/pay_fast/webhook.rb
191
- - lib/paygate_pk/util/html.rb
187
+ - lib/paygate_pk/pay_fast.rb
188
+ - lib/paygate_pk/pay_fast/auth.rb
189
+ - lib/paygate_pk/pay_fast/callback.rb
190
+ - lib/paygate_pk/pay_fast/endpoints.rb
191
+ - lib/paygate_pk/pay_fast/redirect.rb
192
+ - lib/paygate_pk/rails/railtie.rb
193
+ - lib/paygate_pk/rails/view_helpers.rb
194
+ - lib/paygate_pk/util/credentials.rb
192
195
  - lib/paygate_pk/util/security.rb
193
- - lib/paygate_pk/util/signature.rb
196
+ - lib/paygate_pk/util/signature/pay_fast.rb
194
197
  - lib/paygate_pk/version.rb
195
- - paygate_pk.gemspec
196
198
  - sig/paygate_pk.rbs
197
199
  homepage: https://github.com/qbitechs/paygate_pk
198
200
  licenses:
199
201
  - MIT
200
202
  metadata:
201
203
  source_code_uri: https://github.com/qbitechs/paygate_pk
202
- changelog_uri: https://github.com/qbitechs/paygate_pk/blob/main/CHANGELOG.md
204
+ changelog_uri: https://github.com/qbitechs/paygate_pk/blob/master/CHANGELOG.md
203
205
  homepage_url: https://github.com/qbitechs/paygate_pk
206
+ rubygems_mfa_required: 'true'
204
207
  post_install_message:
205
208
  rdoc_options: []
206
209
  require_paths:
207
210
  - lib
208
- - test
209
211
  required_ruby_version: !ruby/object:Gem::Requirement
210
212
  requirements:
211
213
  - - ">="
212
214
  - !ruby/object:Gem::Version
213
- version: 2.6.0
215
+ version: 3.1.0
214
216
  required_rubygems_version: !ruby/object:Gem::Requirement
215
217
  requirements:
216
218
  - - ">="
@@ -220,5 +222,5 @@ requirements: []
220
222
  rubygems_version: 3.4.3
221
223
  signing_key:
222
224
  specification_version: 4
223
- summary: Unified Ruby wrapper for PayFast
225
+ summary: Unified Ruby/Rails client for Pakistani payment gateways (PayFast, Easypaisa)
224
226
  test_files: []
@@ -1,18 +0,0 @@
1
- # lib/paygate_pk/contracts/access_token.rb
2
- # frozen_string_literal: true
3
-
4
- module PaygatePk
5
- module Contracts
6
- # Normalized wrapper for /token response.
7
- # Some docs show only "refresh_token"; we expose both.
8
- BearerToken = Struct.new(
9
- :access_token, # String or nil
10
- :refresh_token, # String or nil
11
- :expiry, # Integer seconds or nil
12
- :code, # Provider code string or nil
13
- :message, # Provider message string or nil
14
- :raw, # Full response Hash
15
- keyword_init: true
16
- )
17
- end
18
- end
@@ -1,8 +0,0 @@
1
- # lib/paygate_pk/contracts/access_token.rb
2
- # frozen_string_literal: true
3
-
4
- module PaygatePk
5
- module Contracts
6
- HostedCheckout = Struct.new(:provider, :basket_id, :amount, :url, :raw, :form, keyword_init: true)
7
- end
8
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaygatePk
4
- module Contracts
5
- Instrument = Struct.new(
6
- :instrument_token, :account_type, :description, :instrument_alias, :raw,
7
- keyword_init: true
8
- )
9
- end
10
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaygatePk
4
- module Contracts
5
- # Normalized server-side notification from PayFast (IPN) or other providers.
6
- WebhookEvent = Struct.new(
7
- :provider, # Symbol e.g., :payfast
8
- :transaction_id, # String or nil
9
- :basket_id, # String
10
- :order_date, # String (YYYY-MM-DD) or Time/Date if you coerce later
11
- :approved, # Boolean (true if err_code == "000")
12
- :code, # Provider code, e.g., "000"
13
- :message, # Human-readable message
14
- :amount, # String/Integer (as received)
15
- :currency, # String "PKR" etc.
16
- :instrument_token, # String or nil (for tokenized flows)
17
- :recurring, # Boolean
18
- :payment_method,
19
- :merchant_amount,
20
- :raw, # Original params Hash (unmodified input)
21
- keyword_init: true
22
- )
23
- end
24
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "date"
4
-
5
- module PaygatePk
6
- module Providers
7
- module PayFast
8
- # Auth client for PayFast API
9
- class Auth < Client
10
- ENDPOINT = "/Ecommerce/api/Transaction/GetAccessToken"
11
-
12
- # Returns Contracts::AccessToken
13
- # Required by PayFast: MERCHANT_ID, SECURED_KEY, BASKET_ID, TXNAMT, CURRENCY_CODE
14
- #
15
- def get_access_token(basket_id:, amount:, currency: PaygatePk.config.default_currency, endpoint: ENDPOINT)
16
- ensure_config!
17
- ensure_args!(basket_id: basket_id, amount: amount, currency: currency)
18
-
19
- # Guide endpoint: .../Ecommerce/api/Transaction/GetAccessToken
20
- resp = http.post(endpoint,
21
- form: payload(basket_id, amount, currency))
22
- token = resp.is_a?(Hash) ? (resp["ACCESS_TOKEN"] || resp["access_token"]) : nil
23
- raise AuthError, "missing ACCESS_TOKEN in response" unless token
24
-
25
- Contracts::AccessToken.new(token: token, raw: resp)
26
- end
27
-
28
- private
29
-
30
- def base_url
31
- config.base_url
32
- end
33
-
34
- def ensure_config!
35
- missing = []
36
- missing << :merchant_id if @config.merchant_id.to_s.strip.empty?
37
- missing << :secured_key if @config.secured_key.to_s.strip.empty?
38
- raise ConfigurationError, "PayFast config missing: #{missing.join(", ")}" unless missing.empty?
39
- end
40
-
41
- def ensure_args!(basket_id:, amount:, currency:)
42
- missing = []
43
- missing << :basket_id if basket_id.to_s.strip.empty?
44
- missing << :amount if amount.nil?
45
- missing << :currency if currency.to_s.strip.empty?
46
- raise ValidationError, "missing required args: #{missing.join(", ")}" unless missing.empty?
47
- end
48
-
49
- def payload(basket_id, amount, currency)
50
- {
51
- "MERCHANT_ID" => @config.merchant_id,
52
- "SECURED_KEY" => @config.secured_key,
53
- "BASKET_ID" => basket_id,
54
- "TXNAMT" => amount.to_s,
55
- "CURRENCY_CODE" => currency
56
- }
57
- end
58
- end
59
- end
60
- end
61
- end