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.
- checksums.yaml +4 -4
- data/.rubocop.yml +34 -3
- data/CHANGELOG.md +213 -0
- data/Gemfile.lock +46 -5
- data/README.md +291 -64
- data/lib/paygate_pk/coercions.rb +64 -0
- data/lib/paygate_pk/config.rb +113 -25
- data/lib/paygate_pk/contracts/access_token.rb +8 -2
- data/lib/paygate_pk/contracts/callback_event.rb +38 -0
- data/lib/paygate_pk/contracts/charge_result.rb +52 -0
- data/lib/paygate_pk/contracts/inquiry_result.rb +58 -0
- data/lib/paygate_pk/contracts/redirect_request.rb +30 -0
- data/lib/paygate_pk/easy_paisa/client.rb +64 -0
- data/lib/paygate_pk/easy_paisa/endpoints.rb +34 -0
- data/lib/paygate_pk/easy_paisa/inquiry.rb +87 -0
- data/lib/paygate_pk/easy_paisa/mobile_account.rb +123 -0
- data/lib/paygate_pk/easy_paisa/otc.rb +146 -0
- data/lib/paygate_pk/easy_paisa.rb +21 -0
- data/lib/paygate_pk/errors.rb +16 -3
- data/lib/paygate_pk/http/client.rb +84 -71
- data/lib/paygate_pk/pay_fast/auth.rb +79 -0
- data/lib/paygate_pk/pay_fast/callback.rb +92 -0
- data/lib/paygate_pk/pay_fast/endpoints.rb +38 -0
- data/lib/paygate_pk/pay_fast/redirect.rb +227 -0
- data/lib/paygate_pk/pay_fast.rb +19 -0
- data/lib/paygate_pk/rails/railtie.rb +19 -0
- data/lib/paygate_pk/rails/view_helpers.rb +159 -0
- data/lib/paygate_pk/util/credentials.rb +27 -0
- data/lib/paygate_pk/util/signature/pay_fast.rb +25 -0
- data/lib/paygate_pk/version.rb +1 -1
- data/lib/paygate_pk.rb +54 -18
- metadata +34 -32
- data/lib/paygate_pk/contracts/bearer_token.rb +0 -18
- data/lib/paygate_pk/contracts/hosted_checkout.rb +0 -8
- data/lib/paygate_pk/contracts/instrument.rb +0 -10
- data/lib/paygate_pk/contracts/webhook_event.rb +0 -24
- data/lib/paygate_pk/providers/pay_fast/auth.rb +0 -61
- data/lib/paygate_pk/providers/pay_fast/checkout.rb +0 -157
- data/lib/paygate_pk/providers/pay_fast/client.rb +0 -53
- data/lib/paygate_pk/providers/pay_fast/tokenization/instrument.rb +0 -63
- data/lib/paygate_pk/providers/pay_fast/tokenization/token.rb +0 -65
- data/lib/paygate_pk/providers/pay_fast/webhook.rb +0 -74
- data/lib/paygate_pk/util/html.rb +0 -42
- data/lib/paygate_pk/util/signature.rb +0 -18
- 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
|
data/lib/paygate_pk/version.rb
CHANGED
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/
|
|
11
|
-
require_relative "paygate_pk/contracts/
|
|
12
|
-
require_relative "paygate_pk/contracts/
|
|
13
|
-
require_relative "paygate_pk/contracts/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
require_relative "paygate_pk/
|
|
17
|
-
require_relative "paygate_pk/
|
|
18
|
-
require_relative "paygate_pk/
|
|
19
|
-
require_relative "paygate_pk/
|
|
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
|
-
|
|
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 ||=
|
|
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:
|
|
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:
|
|
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:
|
|
56
|
+
name: actionview
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
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: '
|
|
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
|
-
|
|
161
|
-
|
|
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/
|
|
180
|
-
- lib/paygate_pk/contracts/
|
|
181
|
-
- lib/paygate_pk/contracts/
|
|
182
|
-
- lib/paygate_pk/contracts/
|
|
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/
|
|
186
|
-
- lib/paygate_pk/
|
|
187
|
-
- lib/paygate_pk/
|
|
188
|
-
- lib/paygate_pk/
|
|
189
|
-
- lib/paygate_pk/
|
|
190
|
-
- lib/paygate_pk/
|
|
191
|
-
- lib/paygate_pk/
|
|
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/
|
|
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:
|
|
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
|
|
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,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
|