paygate_pk 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +31 -3
- data/CHANGELOG.md +126 -0
- data/Gemfile.lock +46 -5
- data/README.md +181 -70
- data/lib/paygate_pk/coercions.rb +46 -0
- data/lib/paygate_pk/config.rb +68 -24
- 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/redirect_request.rb +30 -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 +59 -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 +34 -18
- metadata +25 -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,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module PaygatePk
|
|
7
|
+
module PayFast
|
|
8
|
+
# Builds the redirect form that the customer's browser submits to
|
|
9
|
+
# PayFast's hosted checkout page.
|
|
10
|
+
#
|
|
11
|
+
# Flow (Merchant Integration Guide v2.3 §3.2):
|
|
12
|
+
# 1. Fetch ACCESS_TOKEN server-to-server (delegated to Auth)
|
|
13
|
+
# 2. Assemble all the PostTransaction form fields (mandatory +
|
|
14
|
+
# optional), with PayFast's UPPER_SNAKE_CASE naming
|
|
15
|
+
# 3. Return a Contracts::RedirectRequest the host app renders
|
|
16
|
+
# as an auto-submitting <form>
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
#
|
|
20
|
+
# redirect = PaygatePk::PayFast::Redirect.build(
|
|
21
|
+
# basket_id: "sp-#{payment.id}",
|
|
22
|
+
# amount: 1500,
|
|
23
|
+
# description: "Subscription",
|
|
24
|
+
# customer: { mobile: "03001234567", email: "buyer@x.com", name: "Talha" },
|
|
25
|
+
# success_url: success_url,
|
|
26
|
+
# failure_url: failure_url,
|
|
27
|
+
# checkout_url: webhooks_pay_fast_url, # optional, IPN backend ping
|
|
28
|
+
# recurring: false
|
|
29
|
+
# )
|
|
30
|
+
class Redirect
|
|
31
|
+
# Address-block field mapping. Preserves the SHIPPING_ADDRESS_CITU
|
|
32
|
+
# typo from the PayFast spec — we mirror what the gateway accepts,
|
|
33
|
+
# not what looks correct.
|
|
34
|
+
SHIPPING_FIELDS = {
|
|
35
|
+
name: "SHIPPING_CUSTOMER_NAME",
|
|
36
|
+
address_1: "SHIPPING_ADDRESS_1",
|
|
37
|
+
address_2: "SHIPPING_ADDRESS_2",
|
|
38
|
+
state: "SHIPPING_STATE_PROVINCE",
|
|
39
|
+
city: "SHIPPING_ADDRESS_CITU",
|
|
40
|
+
postal_code: "SHIPPING_POSTALCODE",
|
|
41
|
+
method: "SHIPPING_METHOD"
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
BILLING_FIELDS = {
|
|
45
|
+
name: "BILLING_CUSTOMER_NAME",
|
|
46
|
+
city: "BILLING_ADDRESS_CITY",
|
|
47
|
+
address_1: "BILLING_ADDRESS_1",
|
|
48
|
+
address_2: "BILLING_ADDRESS_2",
|
|
49
|
+
state: "BILLING_STATE_PROVINCE",
|
|
50
|
+
postal_code: "BILLING_POSTALCODE"
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
def self.build(**kwargs)
|
|
54
|
+
new.build(**kwargs)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(config: PaygatePk::PayFast.config, auth: nil)
|
|
58
|
+
@config = config
|
|
59
|
+
@auth = auth
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build(basket_id:, amount:, customer:, success_url:, failure_url:, description:,
|
|
63
|
+
currency: nil, order_date: nil, checkout_url: nil, store_id: nil,
|
|
64
|
+
items: [], recurring: false, tran_type: nil, processing_type: nil,
|
|
65
|
+
instrument_token: nil, shipping: nil, billing: nil, country: nil,
|
|
66
|
+
customer_ip: nil, merchant_customer_id: nil, merchant_user_agent: nil,
|
|
67
|
+
transaction_instrument: nil, extra_fields: {})
|
|
68
|
+
currency ||= PaygatePk.config.default_currency
|
|
69
|
+
order_date_str = Coercions.to_iso_date(order_date) || Date.today.strftime("%Y-%m-%d")
|
|
70
|
+
|
|
71
|
+
ensure_config!
|
|
72
|
+
ensure_args!(basket_id: basket_id, amount: amount, customer: customer,
|
|
73
|
+
success_url: success_url, failure_url: failure_url, description: description)
|
|
74
|
+
|
|
75
|
+
token = auth.call(basket_id: basket_id, amount: amount, currency: currency)
|
|
76
|
+
|
|
77
|
+
fields = build_fields(
|
|
78
|
+
token: token.value,
|
|
79
|
+
basket_id: basket_id,
|
|
80
|
+
amount: amount,
|
|
81
|
+
currency: currency,
|
|
82
|
+
customer: customer,
|
|
83
|
+
success_url: success_url,
|
|
84
|
+
failure_url: failure_url,
|
|
85
|
+
checkout_url: checkout_url,
|
|
86
|
+
description: description,
|
|
87
|
+
order_date_str: order_date_str,
|
|
88
|
+
store_id: store_id,
|
|
89
|
+
items: items,
|
|
90
|
+
recurring: recurring,
|
|
91
|
+
tran_type: tran_type,
|
|
92
|
+
processing_type: processing_type,
|
|
93
|
+
instrument_token: instrument_token,
|
|
94
|
+
transaction_instrument: transaction_instrument,
|
|
95
|
+
shipping: shipping,
|
|
96
|
+
billing: billing,
|
|
97
|
+
country: country,
|
|
98
|
+
customer_ip: customer_ip,
|
|
99
|
+
merchant_customer_id: merchant_customer_id,
|
|
100
|
+
merchant_user_agent: merchant_user_agent,
|
|
101
|
+
extra_fields: extra_fields
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
Contracts::RedirectRequest.new(
|
|
105
|
+
provider: :pay_fast,
|
|
106
|
+
action_url: Endpoints.post_transaction_url(@config.resolved_base_url),
|
|
107
|
+
http_method: :post,
|
|
108
|
+
fields: fields,
|
|
109
|
+
basket_id: basket_id.to_s,
|
|
110
|
+
amount: Coercions.to_amount_string(amount),
|
|
111
|
+
token: token.value,
|
|
112
|
+
raw: token.raw
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def auth
|
|
119
|
+
@auth ||= Auth.new(config: @config)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ensure_config!
|
|
123
|
+
missing = []
|
|
124
|
+
missing << :merchant_id if Coercions.blank?(@config.merchant_id)
|
|
125
|
+
missing << :secured_key if Coercions.blank?(@config.secured_key)
|
|
126
|
+
missing << :merchant_name if Coercions.blank?(@config.merchant_name)
|
|
127
|
+
return if missing.empty?
|
|
128
|
+
|
|
129
|
+
raise PaygatePk::ConfigurationError, "PayFast config missing: #{missing.join(", ")}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
133
|
+
def ensure_args!(basket_id:, amount:, customer:, success_url:, failure_url:, description:)
|
|
134
|
+
missing = []
|
|
135
|
+
missing << :basket_id if Coercions.blank?(basket_id)
|
|
136
|
+
missing << :amount if amount.nil?
|
|
137
|
+
missing << :success_url if Coercions.blank?(success_url)
|
|
138
|
+
missing << :failure_url if Coercions.blank?(failure_url)
|
|
139
|
+
missing << :description if Coercions.blank?(description)
|
|
140
|
+
missing << "customer.mobile" if !customer.is_a?(Hash) || Coercions.blank?(customer[:mobile])
|
|
141
|
+
missing << "customer.email" if !customer.is_a?(Hash) || Coercions.blank?(customer[:email])
|
|
142
|
+
return if missing.empty?
|
|
143
|
+
|
|
144
|
+
raise PaygatePk::ValidationError.new(
|
|
145
|
+
"missing required args: #{missing.join(", ")}",
|
|
146
|
+
details: { missing: missing }
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
150
|
+
|
|
151
|
+
def build_fields(**opts)
|
|
152
|
+
fields = {}
|
|
153
|
+
add_mandatory_fields!(fields, opts)
|
|
154
|
+
add_optional_simple_fields!(fields, opts)
|
|
155
|
+
add_address_fields!(fields, "SHIPPING", opts[:shipping]) if opts[:shipping]
|
|
156
|
+
add_address_fields!(fields, "BILLING", opts[:billing]) if opts[:billing]
|
|
157
|
+
add_items_fields!(fields, opts[:items]) if opts[:items].is_a?(Array) && !opts[:items].empty?
|
|
158
|
+
add_extra_fields!(fields, opts[:extra_fields])
|
|
159
|
+
fields
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Metrics/AbcSize
|
|
163
|
+
def add_mandatory_fields!(fields, opts)
|
|
164
|
+
fields["MERCHANT_ID"] = @config.merchant_id
|
|
165
|
+
fields["MERCHANT_NAME"] = @config.merchant_name
|
|
166
|
+
fields["TOKEN"] = opts[:token]
|
|
167
|
+
fields["PROCCODE"] = "00"
|
|
168
|
+
fields["TXNAMT"] = Coercions.to_amount_string(opts[:amount])
|
|
169
|
+
fields["CUSTOMER_MOBILE_NO"] = opts[:customer][:mobile].to_s
|
|
170
|
+
fields["CUSTOMER_EMAIL_ADDRESS"] = opts[:customer][:email].to_s
|
|
171
|
+
# SIGNATURE and VERSION are documented as "A random string value"
|
|
172
|
+
# in Merchant Integration Guide v2.3 §3.2 — they are not crypto
|
|
173
|
+
# signatures. We generate a fresh random per request.
|
|
174
|
+
fields["SIGNATURE"] = SecureRandom.hex(16)
|
|
175
|
+
fields["VERSION"] = @config.version_string || "paygate_pk/#{PaygatePk::VERSION}"
|
|
176
|
+
fields["TXNDESC"] = opts[:description]
|
|
177
|
+
fields["SUCCESS_URL"] = opts[:success_url]
|
|
178
|
+
fields["FAILURE_URL"] = opts[:failure_url]
|
|
179
|
+
fields["BASKET_ID"] = opts[:basket_id].to_s
|
|
180
|
+
fields["ORDER_DATE"] = opts[:order_date_str]
|
|
181
|
+
fields["CURRENCY_CODE"] = opts[:currency]
|
|
182
|
+
fields["TRAN_TYPE"] = opts[:tran_type] || @config.tran_type
|
|
183
|
+
end
|
|
184
|
+
# rubocop:enable Metrics/AbcSize
|
|
185
|
+
|
|
186
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
187
|
+
def add_optional_simple_fields!(fields, opts)
|
|
188
|
+
fields["CHECKOUT_URL"] = opts[:checkout_url] if opts[:checkout_url]
|
|
189
|
+
store_id = opts[:store_id] || @config.store_id
|
|
190
|
+
fields["STORE_ID"] = store_id if Coercions.present?(store_id)
|
|
191
|
+
fields["RECURRING_TXN"] = opts[:recurring] ? "TRUE" : "FALSE"
|
|
192
|
+
fields["CUSTOMER_NAME"] = opts[:customer][:name].to_s if opts[:customer][:name]
|
|
193
|
+
fields["CUSTOMER_IPADDRESS"] = opts[:customer_ip] if opts[:customer_ip]
|
|
194
|
+
fields["MERCHANT_CUSTOMER_ID"] = opts[:merchant_customer_id] if opts[:merchant_customer_id]
|
|
195
|
+
fields["MERCHANT_USERAGENT"] = opts[:merchant_user_agent] if opts[:merchant_user_agent]
|
|
196
|
+
fields["COUNTRY"] = opts[:country] if opts[:country]
|
|
197
|
+
fields["PROCESSING_TYPE"] = opts[:processing_type] if opts[:processing_type]
|
|
198
|
+
fields["INSTRUMENT_TOKEN"] = opts[:instrument_token] if opts[:instrument_token]
|
|
199
|
+
fields["Transaction_Instrument"] = opts[:transaction_instrument].to_s if opts[:transaction_instrument]
|
|
200
|
+
end
|
|
201
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
202
|
+
|
|
203
|
+
def add_address_fields!(fields, prefix, hash)
|
|
204
|
+
map = prefix == "SHIPPING" ? SHIPPING_FIELDS : BILLING_FIELDS
|
|
205
|
+
map.each do |key, payfast_name|
|
|
206
|
+
v = hash[key]
|
|
207
|
+
fields[payfast_name] = v.to_s if Coercions.present?(v)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def add_items_fields!(fields, items)
|
|
212
|
+
items.each_with_index do |item, i|
|
|
213
|
+
fields["ITEMS[#{i}][SKU]"] = item[:sku].to_s if item[:sku]
|
|
214
|
+
fields["ITEMS[#{i}][NAME]"] = item[:name].to_s if item[:name]
|
|
215
|
+
fields["ITEMS[#{i}][PRICE]"] = Coercions.to_amount_string(item[:price]) if item[:price]
|
|
216
|
+
fields["ITEMS[#{i}][QTY]"] = item[:qty].to_s if item[:qty]
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def add_extra_fields!(fields, extras)
|
|
221
|
+
return unless extras.is_a?(Hash)
|
|
222
|
+
|
|
223
|
+
extras.each { |k, v| fields[k.to_s] = v.to_s }
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -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,59 @@
|
|
|
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
|
+
# Renders the auto-submitting redirect form for any provider that
|
|
12
|
+
# produces a Contracts::RedirectRequest.
|
|
13
|
+
#
|
|
14
|
+
# <%= paygate_pk_redirect_form(@redirect) %>
|
|
15
|
+
#
|
|
16
|
+
# Options:
|
|
17
|
+
# autosubmit: true (default) — emits a script tag that submits
|
|
18
|
+
# the form on page load. Set to false to render a
|
|
19
|
+
# visible "Pay now" button instead.
|
|
20
|
+
# html: { id:, class:, data: { ... }, ... } — passed
|
|
21
|
+
# straight to the <form> tag. `id` falls back to
|
|
22
|
+
# "paygate-pk-redirect-form".
|
|
23
|
+
# submit_label: button label when autosubmit is false.
|
|
24
|
+
def paygate_pk_redirect_form(redirect, autosubmit: true, html: {}, submit_label: SUBMIT_LABEL)
|
|
25
|
+
form_id = html[:id] || DEFAULT_FORM_ID
|
|
26
|
+
form_html = render_form(redirect, form_id, html, autosubmit, submit_label)
|
|
27
|
+
return form_html unless autosubmit
|
|
28
|
+
|
|
29
|
+
form_html + autosubmit_script(form_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def render_form(redirect, form_id, html, autosubmit, submit_label)
|
|
35
|
+
attrs = {
|
|
36
|
+
id: form_id,
|
|
37
|
+
action: redirect.action_url,
|
|
38
|
+
method: redirect.http_method.to_s,
|
|
39
|
+
accept_charset: "UTF-8"
|
|
40
|
+
}.merge(html.except(:id))
|
|
41
|
+
|
|
42
|
+
content_tag(:form, attrs) do
|
|
43
|
+
hidden = redirect.fields.map do |name, value|
|
|
44
|
+
tag.input(type: "hidden", name: name.to_s, value: value.to_s)
|
|
45
|
+
end
|
|
46
|
+
submit = autosubmit ? "".html_safe : tag.button(submit_label, type: "submit")
|
|
47
|
+
safe_join(hidden + [submit])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def autosubmit_script(form_id)
|
|
52
|
+
javascript_tag(
|
|
53
|
+
"document.getElementById(#{form_id.to_json}).submit();",
|
|
54
|
+
nonce: respond_to?(:content_security_policy_nonce) ? content_security_policy_nonce : nil
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
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,53 @@
|
|
|
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
|
+
|
|
6
11
|
require_relative "paygate_pk/http/client"
|
|
7
12
|
|
|
8
|
-
# Contracts used by the endpoint
|
|
9
13
|
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/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"
|
|
14
|
+
require_relative "paygate_pk/contracts/redirect_request"
|
|
15
|
+
require_relative "paygate_pk/contracts/callback_event"
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
require_relative "paygate_pk/pay_fast"
|
|
18
|
+
require_relative "paygate_pk/pay_fast/endpoints"
|
|
19
|
+
require_relative "paygate_pk/pay_fast/auth"
|
|
20
|
+
require_relative "paygate_pk/pay_fast/redirect"
|
|
21
|
+
require_relative "paygate_pk/pay_fast/callback"
|
|
22
|
+
|
|
23
|
+
require_relative "paygate_pk/rails/railtie" if defined?(Rails::Railtie)
|
|
24
|
+
|
|
25
|
+
# Unified Ruby/Rails client for Pakistani payment gateways.
|
|
26
|
+
#
|
|
27
|
+
# PaygatePk.configure do |c|
|
|
28
|
+
# c.pay_fast.environment = :sandbox
|
|
29
|
+
# c.pay_fast.merchant_id = ENV["PAYFAST_MERCHANT_ID"]
|
|
30
|
+
# c.pay_fast.secured_key = ENV["PAYFAST_SECURED_KEY"]
|
|
31
|
+
# c.pay_fast.merchant_name = "Acme Store"
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# redirect = PaygatePk::PayFast::Redirect.build(...)
|
|
35
|
+
# event = PaygatePk::PayFast::Callback.verify!(request.parameters)
|
|
27
36
|
module PaygatePk
|
|
28
37
|
class << self
|
|
29
38
|
def configure
|
|
30
39
|
yield(config)
|
|
31
40
|
config.freeze!
|
|
41
|
+
config
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def config
|
|
35
|
-
@config ||=
|
|
45
|
+
@config ||= Config.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Test/dev helper. Discards the current (possibly frozen) config so
|
|
49
|
+
# that a fresh PaygatePk.configure call can re-seed it.
|
|
50
|
+
def reset_config!
|
|
51
|
+
@config = Config.new
|
|
36
52
|
end
|
|
37
53
|
end
|
|
38
54
|
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.
|
|
4
|
+
version: 1.0.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,41 @@ 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/instrument.rb
|
|
182
|
-
- lib/paygate_pk/contracts/webhook_event.rb
|
|
175
|
+
- lib/paygate_pk/contracts/callback_event.rb
|
|
176
|
+
- lib/paygate_pk/contracts/redirect_request.rb
|
|
183
177
|
- lib/paygate_pk/errors.rb
|
|
184
178
|
- 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/
|
|
179
|
+
- lib/paygate_pk/pay_fast.rb
|
|
180
|
+
- lib/paygate_pk/pay_fast/auth.rb
|
|
181
|
+
- lib/paygate_pk/pay_fast/callback.rb
|
|
182
|
+
- lib/paygate_pk/pay_fast/endpoints.rb
|
|
183
|
+
- lib/paygate_pk/pay_fast/redirect.rb
|
|
184
|
+
- lib/paygate_pk/rails/railtie.rb
|
|
185
|
+
- lib/paygate_pk/rails/view_helpers.rb
|
|
192
186
|
- lib/paygate_pk/util/security.rb
|
|
193
|
-
- lib/paygate_pk/util/signature.rb
|
|
187
|
+
- lib/paygate_pk/util/signature/pay_fast.rb
|
|
194
188
|
- lib/paygate_pk/version.rb
|
|
195
|
-
- paygate_pk.gemspec
|
|
196
189
|
- sig/paygate_pk.rbs
|
|
197
190
|
homepage: https://github.com/qbitechs/paygate_pk
|
|
198
191
|
licenses:
|
|
199
192
|
- MIT
|
|
200
193
|
metadata:
|
|
201
194
|
source_code_uri: https://github.com/qbitechs/paygate_pk
|
|
202
|
-
changelog_uri: https://github.com/qbitechs/paygate_pk/blob/
|
|
195
|
+
changelog_uri: https://github.com/qbitechs/paygate_pk/blob/master/CHANGELOG.md
|
|
203
196
|
homepage_url: https://github.com/qbitechs/paygate_pk
|
|
197
|
+
rubygems_mfa_required: 'true'
|
|
204
198
|
post_install_message:
|
|
205
199
|
rdoc_options: []
|
|
206
200
|
require_paths:
|
|
207
201
|
- lib
|
|
208
|
-
- test
|
|
209
202
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
210
203
|
requirements:
|
|
211
204
|
- - ">="
|
|
212
205
|
- !ruby/object:Gem::Version
|
|
213
|
-
version:
|
|
206
|
+
version: 3.1.0
|
|
214
207
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
208
|
requirements:
|
|
216
209
|
- - ">="
|
|
@@ -220,5 +213,5 @@ requirements: []
|
|
|
220
213
|
rubygems_version: 3.4.3
|
|
221
214
|
signing_key:
|
|
222
215
|
specification_version: 4
|
|
223
|
-
summary: Unified Ruby
|
|
216
|
+
summary: Unified Ruby/Rails client for Pakistani payment gateways (PayFast, Easypaisa)
|
|
224
217
|
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
|